Skip to content

Commit

Permalink
[CPUFREQ] ARM Exynos4210 PM/Suspend compatibility with different boot…
Browse files Browse the repository at this point in the history
…loaders

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>
  • Loading branch information
MyungJoo Ham authored and Dave Jones committed Oct 26, 2011
1 parent 8efd072 commit 0073f53
Showing 1 changed file with 102 additions and 4 deletions.
106 changes: 102 additions & 4 deletions drivers/cpufreq/exynos4210-cpufreq.c
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand All @@ -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,
Expand Down Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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:
Expand Down

0 comments on commit 0073f53

Please sign in to comment.