[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:
parent
8efd072b32
commit
0073f538c1
1 changed files with 102 additions and 4 deletions
|
@ -17,6 +17,8 @@
|
||||||
#include <linux/slab.h>
|
#include <linux/slab.h>
|
||||||
#include <linux/regulator/consumer.h>
|
#include <linux/regulator/consumer.h>
|
||||||
#include <linux/cpufreq.h>
|
#include <linux/cpufreq.h>
|
||||||
|
#include <linux/notifier.h>
|
||||||
|
#include <linux/suspend.h>
|
||||||
|
|
||||||
#include <mach/map.h>
|
#include <mach/map.h>
|
||||||
#include <mach/regs-clock.h>
|
#include <mach/regs-clock.h>
|
||||||
|
@ -36,6 +38,10 @@ static struct regulator *int_regulator;
|
||||||
static struct cpufreq_freqs freqs;
|
static struct cpufreq_freqs freqs;
|
||||||
static unsigned int memtype;
|
static unsigned int memtype;
|
||||||
|
|
||||||
|
static unsigned int locking_frequency;
|
||||||
|
static bool frequency_locked;
|
||||||
|
static DEFINE_MUTEX(cpufreq_lock);
|
||||||
|
|
||||||
enum exynos4_memory_type {
|
enum exynos4_memory_type {
|
||||||
DDR2 = 4,
|
DDR2 = 4,
|
||||||
LPDDR2,
|
LPDDR2,
|
||||||
|
@ -405,22 +411,32 @@ static int exynos4_target(struct cpufreq_policy *policy,
|
||||||
{
|
{
|
||||||
unsigned int index, old_index;
|
unsigned int index, old_index;
|
||||||
unsigned int arm_volt, int_volt;
|
unsigned int arm_volt, int_volt;
|
||||||
|
int err = -EINVAL;
|
||||||
|
|
||||||
freqs.old = exynos4_getspeed(policy->cpu);
|
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,
|
if (cpufreq_frequency_table_target(policy, exynos4_freq_table,
|
||||||
freqs.old, relation, &old_index))
|
freqs.old, relation, &old_index))
|
||||||
return -EINVAL;
|
goto out;
|
||||||
|
|
||||||
if (cpufreq_frequency_table_target(policy, exynos4_freq_table,
|
if (cpufreq_frequency_table_target(policy, exynos4_freq_table,
|
||||||
target_freq, relation, &index))
|
target_freq, relation, &index))
|
||||||
return -EINVAL;
|
goto out;
|
||||||
|
|
||||||
|
err = 0;
|
||||||
|
|
||||||
freqs.new = exynos4_freq_table[index].frequency;
|
freqs.new = exynos4_freq_table[index].frequency;
|
||||||
freqs.cpu = policy->cpu;
|
freqs.cpu = policy->cpu;
|
||||||
|
|
||||||
if (freqs.new == freqs.old)
|
if (freqs.new == freqs.old)
|
||||||
return 0;
|
goto out;
|
||||||
|
|
||||||
/* get the voltage value */
|
/* get the voltage value */
|
||||||
arm_volt = exynos4_volt_table[index].arm_volt;
|
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);
|
cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE);
|
||||||
|
|
||||||
return 0;
|
out:
|
||||||
|
mutex_unlock(&cpufreq_lock);
|
||||||
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef CONFIG_PM
|
#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)
|
static int exynos4_cpufreq_suspend(struct cpufreq_policy *policy)
|
||||||
{
|
{
|
||||||
return 0;
|
return 0;
|
||||||
|
@ -462,6 +484,78 @@ static int exynos4_cpufreq_resume(struct cpufreq_policy *policy)
|
||||||
}
|
}
|
||||||
#endif
|
#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)
|
static int exynos4_cpufreq_cpu_init(struct cpufreq_policy *policy)
|
||||||
{
|
{
|
||||||
int ret;
|
int ret;
|
||||||
|
@ -522,6 +616,8 @@ static int __init exynos4_cpufreq_init(void)
|
||||||
if (IS_ERR(cpu_clk))
|
if (IS_ERR(cpu_clk))
|
||||||
return PTR_ERR(cpu_clk);
|
return PTR_ERR(cpu_clk);
|
||||||
|
|
||||||
|
locking_frequency = exynos4_getspeed(0);
|
||||||
|
|
||||||
moutcore = clk_get(NULL, "moutcore");
|
moutcore = clk_get(NULL, "moutcore");
|
||||||
if (IS_ERR(moutcore))
|
if (IS_ERR(moutcore))
|
||||||
goto out;
|
goto out;
|
||||||
|
@ -561,6 +657,8 @@ static int __init exynos4_cpufreq_init(void)
|
||||||
printk(KERN_DEBUG "%s: memtype= 0x%x\n", __func__, memtype);
|
printk(KERN_DEBUG "%s: memtype= 0x%x\n", __func__, memtype);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
register_pm_notifier(&exynos4_cpufreq_nb);
|
||||||
|
|
||||||
return cpufreq_register_driver(&exynos4_driver);
|
return cpufreq_register_driver(&exynos4_driver);
|
||||||
|
|
||||||
out:
|
out:
|
||||||
|
|
Loading…
Add table
Reference in a new issue