[CPUFREQ] ARM Exynos4210 PM/Suspend compatibility with different bootloaders

We have various bootloaders for Exynos4210 machines. Some of they
set the ARM core frequency at boot time even when the boot is a resume
from suspend-to-RAM. Such changes may create inconsistency in the
data of CPUFREQ driver and have incurred hang issues with suspend-to-RAM.

This patch enables to save and restore CPU frequencies with pm-notifier and
sets the frequency at the initial (boot-time) value so that there wouldn't
be any inconsistency between bootloader and kernel. This patch does not
use CPUFREQ's suspend/resume callbacks because they are syscore-ops, which
do not allow to use mutex that is being used by regulators that are used by
the target function.

This also prevents any CPUFREQ transitions during suspend-resume context,
which could be dangerous at noirq-context along with regulator framework.

Signed-off-by: MyungJoo Ham <myungjoo.ham@samsung.com>
Signed-off-by: Kyungmin Park <kyungmin.park@samsung.com>
Signed-off-by: Dave Jones <davej@redhat.com>
This commit is contained in:
MyungJoo Ham 2011-08-18 19:45:16 +09:00 committed by Dave Jones
parent 8efd072b32
commit 0073f538c1

View File

@ -17,6 +17,8 @@
#include <linux/slab.h>
#include <linux/regulator/consumer.h>
#include <linux/cpufreq.h>
#include <linux/notifier.h>
#include <linux/suspend.h>
#include <mach/map.h>
#include <mach/regs-clock.h>
@ -36,6 +38,10 @@ static struct regulator *int_regulator;
static struct cpufreq_freqs freqs;
static unsigned int memtype;
static unsigned int locking_frequency;
static bool frequency_locked;
static DEFINE_MUTEX(cpufreq_lock);
enum exynos4_memory_type {
DDR2 = 4,
LPDDR2,
@ -405,22 +411,32 @@ static int exynos4_target(struct cpufreq_policy *policy,
{
unsigned int index, old_index;
unsigned int arm_volt, int_volt;
int err = -EINVAL;
freqs.old = exynos4_getspeed(policy->cpu);
mutex_lock(&cpufreq_lock);
if (frequency_locked && target_freq != locking_frequency) {
err = -EAGAIN;
goto out;
}
if (cpufreq_frequency_table_target(policy, exynos4_freq_table,
freqs.old, relation, &old_index))
return -EINVAL;
goto out;
if (cpufreq_frequency_table_target(policy, exynos4_freq_table,
target_freq, relation, &index))
return -EINVAL;
goto out;
err = 0;
freqs.new = exynos4_freq_table[index].frequency;
freqs.cpu = policy->cpu;
if (freqs.new == freqs.old)
return 0;
goto out;
/* get the voltage value */
arm_volt = exynos4_volt_table[index].arm_volt;
@ -447,10 +463,16 @@ static int exynos4_target(struct cpufreq_policy *policy,
cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE);
return 0;
out:
mutex_unlock(&cpufreq_lock);
return err;
}
#ifdef CONFIG_PM
/*
* These suspend/resume are used as syscore_ops, it is already too
* late to set regulator voltages at this stage.
*/
static int exynos4_cpufreq_suspend(struct cpufreq_policy *policy)
{
return 0;
@ -462,6 +484,78 @@ static int exynos4_cpufreq_resume(struct cpufreq_policy *policy)
}
#endif
/**
* exynos4_cpufreq_pm_notifier - block CPUFREQ's activities in suspend-resume
* context
* @notifier
* @pm_event
* @v
*
* While frequency_locked == true, target() ignores every frequency but
* locking_frequency. The locking_frequency value is the initial frequency,
* which is set by the bootloader. In order to eliminate possible
* inconsistency in clock values, we save and restore frequencies during
* suspend and resume and block CPUFREQ activities. Note that the standard
* suspend/resume cannot be used as they are too deep (syscore_ops) for
* regulator actions.
*/
static int exynos4_cpufreq_pm_notifier(struct notifier_block *notifier,
unsigned long pm_event, void *v)
{
struct cpufreq_policy *policy = cpufreq_cpu_get(0); /* boot CPU */
static unsigned int saved_frequency;
unsigned int temp;
mutex_lock(&cpufreq_lock);
switch (pm_event) {
case PM_SUSPEND_PREPARE:
if (frequency_locked)
goto out;
frequency_locked = true;
if (locking_frequency) {
saved_frequency = exynos4_getspeed(0);
mutex_unlock(&cpufreq_lock);
exynos4_target(policy, locking_frequency,
CPUFREQ_RELATION_H);
mutex_lock(&cpufreq_lock);
}
break;
case PM_POST_SUSPEND:
if (saved_frequency) {
/*
* While frequency_locked, only locking_frequency
* is valid for target(). In order to use
* saved_frequency while keeping frequency_locked,
* we temporarly overwrite locking_frequency.
*/
temp = locking_frequency;
locking_frequency = saved_frequency;
mutex_unlock(&cpufreq_lock);
exynos4_target(policy, locking_frequency,
CPUFREQ_RELATION_H);
mutex_lock(&cpufreq_lock);
locking_frequency = temp;
}
frequency_locked = false;
break;
}
out:
mutex_unlock(&cpufreq_lock);
return NOTIFY_OK;
}
static struct notifier_block exynos4_cpufreq_nb = {
.notifier_call = exynos4_cpufreq_pm_notifier,
};
static int exynos4_cpufreq_cpu_init(struct cpufreq_policy *policy)
{
int ret;
@ -522,6 +616,8 @@ static int __init exynos4_cpufreq_init(void)
if (IS_ERR(cpu_clk))
return PTR_ERR(cpu_clk);
locking_frequency = exynos4_getspeed(0);
moutcore = clk_get(NULL, "moutcore");
if (IS_ERR(moutcore))
goto out;
@ -561,6 +657,8 @@ static int __init exynos4_cpufreq_init(void)
printk(KERN_DEBUG "%s: memtype= 0x%x\n", __func__, memtype);
}
register_pm_notifier(&exynos4_cpufreq_nb);
return cpufreq_register_driver(&exynos4_driver);
out: