diff options
author | MyungJoo Ham <myungjoo.ham@samsung.com> | 2011-08-18 06:45:16 -0400 |
---|---|---|
committer | Dave Jones <davej@redhat.com> | 2011-10-26 17:19:46 -0400 |
commit | 0073f538c1c35f996982b583f5de7a6a43408b9b (patch) | |
tree | 035c9975f1d65d265ae4165f31b31325fe269dfd | |
parent | 8efd072b32d67436413e98e25e9a316216e88900 (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>
-rw-r--r-- | drivers/cpufreq/exynos4210-cpufreq.c | 106 |
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; | |||
36 | static struct cpufreq_freqs freqs; | 38 | static struct cpufreq_freqs freqs; |
37 | static unsigned int memtype; | 39 | static unsigned int memtype; |
38 | 40 | ||
41 | static unsigned int locking_frequency; | ||
42 | static bool frequency_locked; | ||
43 | static DEFINE_MUTEX(cpufreq_lock); | ||
44 | |||
39 | enum exynos4_memory_type { | 45 | enum 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; | 466 | out: |
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 | */ | ||
454 | static int exynos4_cpufreq_suspend(struct cpufreq_policy *policy) | 476 | static 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 | */ | ||
502 | static 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 | } | ||
549 | out: | ||
550 | mutex_unlock(&cpufreq_lock); | ||
551 | |||
552 | return NOTIFY_OK; | ||
553 | } | ||
554 | |||
555 | static struct notifier_block exynos4_cpufreq_nb = { | ||
556 | .notifier_call = exynos4_cpufreq_pm_notifier, | ||
557 | }; | ||
558 | |||
465 | static int exynos4_cpufreq_cpu_init(struct cpufreq_policy *policy) | 559 | static 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 | ||
566 | out: | 664 | out: |