aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/cpufreq/exynos4210-cpufreq.c
diff options
context:
space:
mode:
authorMyungJoo Ham <myungjoo.ham@samsung.com>2011-08-18 06:45:16 -0400
committerDave Jones <davej@redhat.com>2011-10-26 17:19:46 -0400
commit0073f538c1c35f996982b583f5de7a6a43408b9b (patch)
tree035c9975f1d65d265ae4165f31b31325fe269dfd /drivers/cpufreq/exynos4210-cpufreq.c
parent8efd072b32d67436413e98e25e9a316216e88900 (diff)
[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>
Diffstat (limited to 'drivers/cpufreq/exynos4210-cpufreq.c')
-rw-r--r--drivers/cpufreq/exynos4210-cpufreq.c106
1 files changed, 102 insertions, 4 deletions
diff --git a/drivers/cpufreq/exynos4210-cpufreq.c b/drivers/cpufreq/exynos4210-cpufreq.c
index 6f887573ce94..ab9741fab92e 100644
--- a/drivers/cpufreq/exynos4210-cpufreq.c
+++ b/drivers/cpufreq/exynos4210-cpufreq.c
@@ -17,6 +17,8 @@
17#include <linux/slab.h> 17#include <linux/slab.h>
18#include <linux/regulator/consumer.h> 18#include <linux/regulator/consumer.h>
19#include <linux/cpufreq.h> 19#include <linux/cpufreq.h>
20#include <linux/notifier.h>
21#include <linux/suspend.h>
20 22
21#include <mach/map.h> 23#include <mach/map.h>
22#include <mach/regs-clock.h> 24#include <mach/regs-clock.h>
@@ -36,6 +38,10 @@ static struct regulator *int_regulator;
36static struct cpufreq_freqs freqs; 38static struct cpufreq_freqs freqs;
37static unsigned int memtype; 39static unsigned int memtype;
38 40
41static unsigned int locking_frequency;
42static bool frequency_locked;
43static DEFINE_MUTEX(cpufreq_lock);
44
39enum exynos4_memory_type { 45enum exynos4_memory_type {
40 DDR2 = 4, 46 DDR2 = 4,
41 LPDDR2, 47 LPDDR2,
@@ -405,22 +411,32 @@ static int exynos4_target(struct cpufreq_policy *policy,
405{ 411{
406 unsigned int index, old_index; 412 unsigned int index, old_index;
407 unsigned int arm_volt, int_volt; 413 unsigned int arm_volt, int_volt;
414 int err = -EINVAL;
408 415
409 freqs.old = exynos4_getspeed(policy->cpu); 416 freqs.old = exynos4_getspeed(policy->cpu);
410 417
418 mutex_lock(&cpufreq_lock);
419
420 if (frequency_locked && target_freq != locking_frequency) {
421 err = -EAGAIN;
422 goto out;
423 }
424
411 if (cpufreq_frequency_table_target(policy, exynos4_freq_table, 425 if (cpufreq_frequency_table_target(policy, exynos4_freq_table,
412 freqs.old, relation, &old_index)) 426 freqs.old, relation, &old_index))
413 return -EINVAL; 427 goto out;
414 428
415 if (cpufreq_frequency_table_target(policy, exynos4_freq_table, 429 if (cpufreq_frequency_table_target(policy, exynos4_freq_table,
416 target_freq, relation, &index)) 430 target_freq, relation, &index))
417 return -EINVAL; 431 goto out;
432
433 err = 0;
418 434
419 freqs.new = exynos4_freq_table[index].frequency; 435 freqs.new = exynos4_freq_table[index].frequency;
420 freqs.cpu = policy->cpu; 436 freqs.cpu = policy->cpu;
421 437
422 if (freqs.new == freqs.old) 438 if (freqs.new == freqs.old)
423 return 0; 439 goto out;
424 440
425 /* get the voltage value */ 441 /* get the voltage value */
426 arm_volt = exynos4_volt_table[index].arm_volt; 442 arm_volt = exynos4_volt_table[index].arm_volt;
@@ -447,10 +463,16 @@ static int exynos4_target(struct cpufreq_policy *policy,
447 463
448 cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE); 464 cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE);
449 465
450 return 0; 466out:
467 mutex_unlock(&cpufreq_lock);
468 return err;
451} 469}
452 470
453#ifdef CONFIG_PM 471#ifdef CONFIG_PM
472/*
473 * These suspend/resume are used as syscore_ops, it is already too
474 * late to set regulator voltages at this stage.
475 */
454static int exynos4_cpufreq_suspend(struct cpufreq_policy *policy) 476static int exynos4_cpufreq_suspend(struct cpufreq_policy *policy)
455{ 477{
456 return 0; 478 return 0;
@@ -462,6 +484,78 @@ static int exynos4_cpufreq_resume(struct cpufreq_policy *policy)
462} 484}
463#endif 485#endif
464 486
487/**
488 * exynos4_cpufreq_pm_notifier - block CPUFREQ's activities in suspend-resume
489 * context
490 * @notifier
491 * @pm_event
492 * @v
493 *
494 * While frequency_locked == true, target() ignores every frequency but
495 * locking_frequency. The locking_frequency value is the initial frequency,
496 * which is set by the bootloader. In order to eliminate possible
497 * inconsistency in clock values, we save and restore frequencies during
498 * suspend and resume and block CPUFREQ activities. Note that the standard
499 * suspend/resume cannot be used as they are too deep (syscore_ops) for
500 * regulator actions.
501 */
502static int exynos4_cpufreq_pm_notifier(struct notifier_block *notifier,
503 unsigned long pm_event, void *v)
504{
505 struct cpufreq_policy *policy = cpufreq_cpu_get(0); /* boot CPU */
506 static unsigned int saved_frequency;
507 unsigned int temp;
508
509 mutex_lock(&cpufreq_lock);
510 switch (pm_event) {
511 case PM_SUSPEND_PREPARE:
512 if (frequency_locked)
513 goto out;
514 frequency_locked = true;
515
516 if (locking_frequency) {
517 saved_frequency = exynos4_getspeed(0);
518
519 mutex_unlock(&cpufreq_lock);
520 exynos4_target(policy, locking_frequency,
521 CPUFREQ_RELATION_H);
522 mutex_lock(&cpufreq_lock);
523 }
524
525 break;
526 case PM_POST_SUSPEND:
527
528 if (saved_frequency) {
529 /*
530 * While frequency_locked, only locking_frequency
531 * is valid for target(). In order to use
532 * saved_frequency while keeping frequency_locked,
533 * we temporarly overwrite locking_frequency.
534 */
535 temp = locking_frequency;
536 locking_frequency = saved_frequency;
537
538 mutex_unlock(&cpufreq_lock);
539 exynos4_target(policy, locking_frequency,
540 CPUFREQ_RELATION_H);
541 mutex_lock(&cpufreq_lock);
542
543 locking_frequency = temp;
544 }
545
546 frequency_locked = false;
547 break;
548 }
549out:
550 mutex_unlock(&cpufreq_lock);
551
552 return NOTIFY_OK;
553}
554
555static struct notifier_block exynos4_cpufreq_nb = {
556 .notifier_call = exynos4_cpufreq_pm_notifier,
557};
558
465static int exynos4_cpufreq_cpu_init(struct cpufreq_policy *policy) 559static int exynos4_cpufreq_cpu_init(struct cpufreq_policy *policy)
466{ 560{
467 int ret; 561 int ret;
@@ -522,6 +616,8 @@ static int __init exynos4_cpufreq_init(void)
522 if (IS_ERR(cpu_clk)) 616 if (IS_ERR(cpu_clk))
523 return PTR_ERR(cpu_clk); 617 return PTR_ERR(cpu_clk);
524 618
619 locking_frequency = exynos4_getspeed(0);
620
525 moutcore = clk_get(NULL, "moutcore"); 621 moutcore = clk_get(NULL, "moutcore");
526 if (IS_ERR(moutcore)) 622 if (IS_ERR(moutcore))
527 goto out; 623 goto out;
@@ -561,6 +657,8 @@ static int __init exynos4_cpufreq_init(void)
561 printk(KERN_DEBUG "%s: memtype= 0x%x\n", __func__, memtype); 657 printk(KERN_DEBUG "%s: memtype= 0x%x\n", __func__, memtype);
562 } 658 }
563 659
660 register_pm_notifier(&exynos4_cpufreq_nb);
661
564 return cpufreq_register_driver(&exynos4_driver); 662 return cpufreq_register_driver(&exynos4_driver);
565 663
566out: 664out: