Merge branch 'develop-3.0-rk30' of ssh://10.10.10.29/rk/kernel into develop-3.0-rk30

This commit is contained in:
yxj
2012-02-22 14:39:17 +08:00
9 changed files with 448 additions and 17 deletions

View File

@@ -29,6 +29,7 @@ config ARM
select HAVE_GENERIC_HARDIRQS
select HAVE_SPARSE_IRQ
select GENERIC_IRQ_SHOW
select CPU_PM if (SUSPEND || CPU_IDLE)
help
The ARM series is a line of low-power-consumption RISC chip designs
licensed by ARM Ltd and targeted at embedded applications and

View File

@@ -97,8 +97,22 @@ extern void cpu_resume(void);
#ifdef CONFIG_MMU
#ifdef CONFIG_SMP
#define cpu_switch_mm(pgd, mm) \
({ \
unsigned long flags; \
local_irq_save(flags); \
cpu_do_switch_mm(virt_to_phys(pgd), mm); \
local_irq_restore(flags); \
})
#else /* SMP */
#define cpu_switch_mm(pgd,mm) cpu_do_switch_mm(virt_to_phys(pgd),mm)
#endif
#define cpu_get_pgd() \
({ \
unsigned long pg; \

View File

@@ -10,13 +10,17 @@
*/
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/clk.h>
#include <linux/cpufreq.h>
#include <linux/delay.h>
#include <linux/device.h>
#include <linux/err.h>
#include <linux/smp.h>
#include <linux/jiffies.h>
#include <linux/clockchips.h>
#include <linux/irq.h>
#include <linux/io.h>
#include <linux/percpu.h>
#include <asm/smp_twd.h>
#include <asm/hardware/gic.h>
@@ -24,7 +28,9 @@
/* set up by the platform code */
void __iomem *twd_base;
static struct clk *twd_clk;
static unsigned long twd_timer_rate;
static DEFINE_PER_CPU(struct clock_event_device *, twd_ce);
static void twd_set_mode(enum clock_event_mode mode,
struct clock_event_device *clk)
@@ -80,6 +86,48 @@ int twd_timer_ack(void)
return 0;
}
/*
* Updates clockevent frequency when the cpu frequency changes.
* Called on the cpu that is changing frequency with interrupts disabled.
*/
static void twd_update_frequency(void *data)
{
twd_timer_rate = clk_get_rate(twd_clk);
clockevents_update_freq(__get_cpu_var(twd_ce), twd_timer_rate);
}
static int twd_cpufreq_transition(struct notifier_block *nb,
unsigned long state, void *data)
{
struct cpufreq_freqs *freqs = data;
/*
* The twd clock events must be reprogrammed to account for the new
* frequency. The timer is local to a cpu, so cross-call to the
* changing cpu.
*/
if (state == CPUFREQ_POSTCHANGE || state == CPUFREQ_RESUMECHANGE)
smp_call_function_single(freqs->cpu, twd_update_frequency,
NULL, 1);
return NOTIFY_OK;
}
static struct notifier_block twd_cpufreq_nb = {
.notifier_call = twd_cpufreq_transition,
};
static int twd_cpufreq_init(void)
{
if (!IS_ERR_OR_NULL(twd_clk))
return cpufreq_register_notifier(&twd_cpufreq_nb,
CPUFREQ_TRANSITION_NOTIFIER);
return 0;
}
core_initcall(twd_cpufreq_init);
static void __cpuinit twd_calibrate_rate(void)
{
unsigned long count;
@@ -124,7 +172,18 @@ static void __cpuinit twd_calibrate_rate(void)
*/
void __cpuinit twd_timer_setup(struct clock_event_device *clk)
{
twd_calibrate_rate();
if (twd_clk == NULL) {
twd_clk = clk_get_sys("smp_twd", NULL);
if (IS_ERR_OR_NULL(twd_clk))
pr_warn("%s: no clock found\n", __func__);
}
if (!IS_ERR_OR_NULL(twd_clk))
twd_timer_rate = clk_get_rate(twd_clk);
else
twd_calibrate_rate();
__raw_writel(0, twd_base + TWD_TIMER_CONTROL);
clk->name = "local_timer";
clk->features = CLOCK_EVT_FEAT_PERIODIC | CLOCK_EVT_FEAT_ONESHOT |
@@ -132,13 +191,11 @@ void __cpuinit twd_timer_setup(struct clock_event_device *clk)
clk->rating = 350;
clk->set_mode = twd_set_mode;
clk->set_next_event = twd_set_next_event;
clk->shift = 20;
clk->mult = div_sc(twd_timer_rate, NSEC_PER_SEC, clk->shift);
clk->max_delta_ns = clockevent_delta2ns(0xffffffff, clk);
clk->min_delta_ns = clockevent_delta2ns(0xf, clk);
__get_cpu_var(twd_ce) = clk;
clockevents_config_and_register(clk, twd_timer_rate, 0xf, 0xffffffff);
/* Make sure our local interrupt controller has this enabled */
gic_enable_ppi(clk->irq);
clockevents_register_device(clk);
}

View File

@@ -99,8 +99,7 @@ static void reset_context(void *info)
set_mm_context(mm, asid);
/* set the new ASID */
asm("mcr p15, 0, %0, c13, c0, 1\n" : : "r" (mm->context.id));
isb();
cpu_switch_mm(mm->pgd, mm);
}
#else

View File

@@ -11,6 +11,7 @@
#include <linux/module.h>
#include <linux/types.h>
#include <linux/cpu.h>
#include <linux/cpu_pm.h>
#include <linux/kernel.h>
#include <linux/notifier.h>
#include <linux/signal.h>
@@ -403,9 +404,7 @@ static void vfp_enable(void *unused)
set_copro_access(access | CPACC_FULL(10) | CPACC_FULL(11));
}
#ifdef CONFIG_PM
#include <linux/syscore_ops.h>
#ifdef CONFIG_CPU_PM
static int vfp_pm_suspend(void)
{
struct thread_info *ti = current_thread_info();
@@ -439,19 +438,33 @@ static void vfp_pm_resume(void)
fmxr(FPEXC, fmrx(FPEXC) & ~FPEXC_EN);
}
static struct syscore_ops vfp_pm_syscore_ops = {
.suspend = vfp_pm_suspend,
.resume = vfp_pm_resume,
static int vfp_cpu_pm_notifier(struct notifier_block *self, unsigned long cmd,
void *v)
{
switch (cmd) {
case CPU_PM_ENTER:
vfp_pm_suspend();
break;
case CPU_PM_ENTER_FAILED:
case CPU_PM_EXIT:
vfp_pm_resume();
break;
}
return NOTIFY_OK;
}
static struct notifier_block vfp_cpu_pm_notifier_block = {
.notifier_call = vfp_cpu_pm_notifier,
};
static void vfp_pm_init(void)
{
register_syscore_ops(&vfp_pm_syscore_ops);
cpu_pm_register_notifier(&vfp_cpu_pm_notifier_block);
}
#else
static inline void vfp_pm_init(void) { }
#endif /* CONFIG_PM */
#endif /* CONFIG_CPU_PM */
void vfp_sync_hwstate(struct thread_info *thread)
{

109
include/linux/cpu_pm.h Normal file
View File

@@ -0,0 +1,109 @@
/*
* Copyright (C) 2011 Google, Inc.
*
* Author:
* Colin Cross <ccross@android.com>
*
* This software is licensed under the terms of the GNU General Public
* License version 2, as published by the Free Software Foundation, and
* may be copied, distributed, and modified under those terms.
*
* 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.
*
*/
#ifndef _LINUX_CPU_PM_H
#define _LINUX_CPU_PM_H
#include <linux/kernel.h>
#include <linux/notifier.h>
/*
* When a CPU goes to a low power state that turns off power to the CPU's
* power domain, the contents of some blocks (floating point coprocessors,
* interrupt controllers, caches, timers) in the same power domain can
* be lost. The cpm_pm notifiers provide a method for platform idle, suspend,
* and hotplug implementations to notify the drivers for these blocks that
* they may be reset.
*
* All cpu_pm notifications must be called with interrupts disabled.
*
* The notifications are split into two classes: CPU notifications and CPU
* cluster notifications.
*
* CPU notifications apply to a single CPU and must be called on the affected
* CPU. They are used to save per-cpu context for affected blocks.
*
* CPU cluster notifications apply to all CPUs in a single power domain. They
* are used to save any global context for affected blocks, and must be called
* after all the CPUs in the power domain have been notified of the low power
* state.
*/
/*
* Event codes passed as unsigned long val to notifier calls
*/
enum cpu_pm_event {
/* A single cpu is entering a low power state */
CPU_PM_ENTER,
/* A single cpu failed to enter a low power state */
CPU_PM_ENTER_FAILED,
/* A single cpu is exiting a low power state */
CPU_PM_EXIT,
/* A cpu power domain is entering a low power state */
CPU_CLUSTER_PM_ENTER,
/* A cpu power domain failed to enter a low power state */
CPU_CLUSTER_PM_ENTER_FAILED,
/* A cpu power domain is exiting a low power state */
CPU_CLUSTER_PM_EXIT,
};
#ifdef CONFIG_CPU_PM
int cpu_pm_register_notifier(struct notifier_block *nb);
int cpu_pm_unregister_notifier(struct notifier_block *nb);
int cpu_pm_enter(void);
int cpu_pm_exit(void);
int cpu_cluster_pm_enter(void);
int cpu_cluster_pm_exit(void);
#else
static inline int cpu_pm_register_notifier(struct notifier_block *nb)
{
return 0;
}
static inline int cpu_pm_unregister_notifier(struct notifier_block *nb)
{
return 0;
}
static inline int cpu_pm_enter(void)
{
return 0;
}
static inline int cpu_pm_exit(void)
{
return 0;
}
static inline int cpu_cluster_pm_enter(void)
{
return 0;
}
static inline int cpu_cluster_pm_exit(void)
{
return 0;
}
#endif
#endif

View File

@@ -101,6 +101,7 @@ obj-$(CONFIG_RING_BUFFER) += trace/
obj-$(CONFIG_TRACEPOINTS) += trace/
obj-$(CONFIG_SMP) += sched_cpupri.o
obj-$(CONFIG_IRQ_WORK) += irq_work.o
obj-$(CONFIG_CPU_PM) += cpu_pm.o
obj-$(CONFIG_PERF_EVENTS) += events/

233
kernel/cpu_pm.c Normal file
View File

@@ -0,0 +1,233 @@
/*
* Copyright (C) 2011 Google, Inc.
*
* Author:
* Colin Cross <ccross@android.com>
*
* This software is licensed under the terms of the GNU General Public
* License version 2, as published by the Free Software Foundation, and
* may be copied, distributed, and modified under those terms.
*
* 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.
*
*/
#include <linux/kernel.h>
#include <linux/cpu_pm.h>
#include <linux/module.h>
#include <linux/notifier.h>
#include <linux/spinlock.h>
#include <linux/syscore_ops.h>
static DEFINE_RWLOCK(cpu_pm_notifier_lock);
static RAW_NOTIFIER_HEAD(cpu_pm_notifier_chain);
static int cpu_pm_notify(enum cpu_pm_event event, int nr_to_call, int *nr_calls)
{
int ret;
ret = __raw_notifier_call_chain(&cpu_pm_notifier_chain, event, NULL,
nr_to_call, nr_calls);
return notifier_to_errno(ret);
}
/**
* cpu_pm_register_notifier - register a driver with cpu_pm
* @nb: notifier block to register
*
* Add a driver to a list of drivers that are notified about
* CPU and CPU cluster low power entry and exit.
*
* This function may sleep, and has the same return conditions as
* raw_notifier_chain_register.
*/
int cpu_pm_register_notifier(struct notifier_block *nb)
{
unsigned long flags;
int ret;
write_lock_irqsave(&cpu_pm_notifier_lock, flags);
ret = raw_notifier_chain_register(&cpu_pm_notifier_chain, nb);
write_unlock_irqrestore(&cpu_pm_notifier_lock, flags);
return ret;
}
EXPORT_SYMBOL_GPL(cpu_pm_register_notifier);
/**
* cpu_pm_unregister_notifier - unregister a driver with cpu_pm
* @nb: notifier block to be unregistered
*
* Remove a driver from the CPU PM notifier list.
*
* This function may sleep, and has the same return conditions as
* raw_notifier_chain_unregister.
*/
int cpu_pm_unregister_notifier(struct notifier_block *nb)
{
unsigned long flags;
int ret;
write_lock_irqsave(&cpu_pm_notifier_lock, flags);
ret = raw_notifier_chain_unregister(&cpu_pm_notifier_chain, nb);
write_unlock_irqrestore(&cpu_pm_notifier_lock, flags);
return ret;
}
EXPORT_SYMBOL_GPL(cpu_pm_unregister_notifier);
/**
* cpm_pm_enter - CPU low power entry notifier
*
* Notifies listeners that a single CPU is entering a low power state that may
* cause some blocks in the same power domain as the cpu to reset.
*
* Must be called on the affected CPU with interrupts disabled. Platform is
* responsible for ensuring that cpu_pm_enter is not called twice on the same
* CPU before cpu_pm_exit is called. Notified drivers can include VFP
* co-processor, interrupt controller and it's PM extensions, local CPU
* timers context save/restore which shouldn't be interrupted. Hence it
* must be called with interrupts disabled.
*
* Return conditions are same as __raw_notifier_call_chain.
*/
int cpu_pm_enter(void)
{
int nr_calls;
int ret = 0;
read_lock(&cpu_pm_notifier_lock);
ret = cpu_pm_notify(CPU_PM_ENTER, -1, &nr_calls);
if (ret)
/*
* Inform listeners (nr_calls - 1) about failure of CPU PM
* PM entry who are notified earlier to prepare for it.
*/
cpu_pm_notify(CPU_PM_ENTER_FAILED, nr_calls - 1, NULL);
read_unlock(&cpu_pm_notifier_lock);
return ret;
}
EXPORT_SYMBOL_GPL(cpu_pm_enter);
/**
* cpm_pm_exit - CPU low power exit notifier
*
* Notifies listeners that a single CPU is exiting a low power state that may
* have caused some blocks in the same power domain as the cpu to reset.
*
* Notified drivers can include VFP co-processor, interrupt controller
* and it's PM extensions, local CPU timers context save/restore which
* shouldn't be interrupted. Hence it must be called with interrupts disabled.
*
* Return conditions are same as __raw_notifier_call_chain.
*/
int cpu_pm_exit(void)
{
int ret;
read_lock(&cpu_pm_notifier_lock);
ret = cpu_pm_notify(CPU_PM_EXIT, -1, NULL);
read_unlock(&cpu_pm_notifier_lock);
return ret;
}
EXPORT_SYMBOL_GPL(cpu_pm_exit);
/**
* cpm_cluster_pm_enter - CPU cluster low power entry notifier
*
* Notifies listeners that all cpus in a power domain are entering a low power
* state that may cause some blocks in the same power domain to reset.
*
* Must be called after cpu_pm_enter has been called on all cpus in the power
* domain, and before cpu_pm_exit has been called on any cpu in the power
* domain. Notified drivers can include VFP co-processor, interrupt controller
* and it's PM extensions, local CPU timers context save/restore which
* shouldn't be interrupted. Hence it must be called with interrupts disabled.
*
* Must be called with interrupts disabled.
*
* Return conditions are same as __raw_notifier_call_chain.
*/
int cpu_cluster_pm_enter(void)
{
int nr_calls;
int ret = 0;
read_lock(&cpu_pm_notifier_lock);
ret = cpu_pm_notify(CPU_CLUSTER_PM_ENTER, -1, &nr_calls);
if (ret)
/*
* Inform listeners (nr_calls - 1) about failure of CPU cluster
* PM entry who are notified earlier to prepare for it.
*/
cpu_pm_notify(CPU_CLUSTER_PM_ENTER_FAILED, nr_calls - 1, NULL);
read_unlock(&cpu_pm_notifier_lock);
return ret;
}
EXPORT_SYMBOL_GPL(cpu_cluster_pm_enter);
/**
* cpm_cluster_pm_exit - CPU cluster low power exit notifier
*
* Notifies listeners that all cpus in a power domain are exiting form a
* low power state that may have caused some blocks in the same power domain
* to reset.
*
* Must be called after cpu_pm_exit has been called on all cpus in the power
* domain, and before cpu_pm_exit has been called on any cpu in the power
* domain. Notified drivers can include VFP co-processor, interrupt controller
* and it's PM extensions, local CPU timers context save/restore which
* shouldn't be interrupted. Hence it must be called with interrupts disabled.
*
* Return conditions are same as __raw_notifier_call_chain.
*/
int cpu_cluster_pm_exit(void)
{
int ret;
read_lock(&cpu_pm_notifier_lock);
ret = cpu_pm_notify(CPU_CLUSTER_PM_EXIT, -1, NULL);
read_unlock(&cpu_pm_notifier_lock);
return ret;
}
EXPORT_SYMBOL_GPL(cpu_cluster_pm_exit);
#ifdef CONFIG_PM
static int cpu_pm_suspend(void)
{
int ret;
ret = cpu_pm_enter();
if (ret)
return ret;
ret = cpu_cluster_pm_enter();
return ret;
}
static void cpu_pm_resume(void)
{
cpu_cluster_pm_exit();
cpu_pm_exit();
}
static struct syscore_ops cpu_pm_syscore_ops = {
.suspend = cpu_pm_suspend,
.resume = cpu_pm_resume,
};
static int cpu_pm_init(void)
{
register_syscore_ops(&cpu_pm_syscore_ops);
return 0;
}
core_initcall(cpu_pm_init);
#endif

View File

@@ -295,6 +295,10 @@ config PM_RUNTIME_CLK
def_bool y
depends on PM_RUNTIME && HAVE_CLK
config CPU_PM
bool
depends on SUSPEND || CPU_IDLE
config SUSPEND_TIME
bool "Log time spent in suspend"
---help---