From 43eae3bb1ab429b1b88bd8c8558b685987f09521 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Neum=C3=BCller?= Date: Tue, 15 Dec 2015 14:51:23 +0100 Subject: cpuidle-exynos4: Don't get stuck at 150 MHz. The following scenario leads to the i9100 getting stuck at 150 MHz: 1. Initially, only CPU0 is online. 2. CPU0 starts to bring CPU1 online. 3. CPU0 is waiting for CPU1 to come online, and meanwhile enters idle. Since it is the only online CPU, it lowers the CPU speed to save power. 4. CPU1 goes online (NOTE: While CPU0 is still idling!) Note that local_irq_disable does not help here because CPU1 could already be starting up (most_likely idle could be entered on CPU0 while in "wait_for_completion_timeout(&cpu_running, ...)" in arch/arm/kernel/smp.c:126. This patch treats CPUs as online ("onlining") as soon as the CPU_UP_PREPARE notifier is fired. Change-Id: I7964ec3ea79084c3050db79d7d6c89db9f378326 (cherry picked from commit c96e5ebbfa00c07a7867b68799ac4c24a7a1535f) --- arch/arm/mach-exynos/cpuidle-exynos4.c | 79 ++++++++++++++++++++++++++++++++-- 1 file changed, 76 insertions(+), 3 deletions(-) diff --git a/arch/arm/mach-exynos/cpuidle-exynos4.c b/arch/arm/mach-exynos/cpuidle-exynos4.c index 88b941f..d58c3f1 100644 --- a/arch/arm/mach-exynos/cpuidle-exynos4.c +++ b/arch/arm/mach-exynos/cpuidle-exynos4.c @@ -515,6 +515,40 @@ static struct sleep_save exynos4210_set_clksrc[] = { { .reg = EXYNOS4_CLKSRC_MASK_LCD1 , .val = 0x00001111, }, }; + +static DEFINE_SPINLOCK(online_lock); +static int n_onlining_cpus_impl; + +static void add_onlininig_cpu(void) +{ + spin_lock(&online_lock); + ++n_onlining_cpus_impl; + spin_unlock(&online_lock); +} + +static void remove_onlininig_cpu(void) +{ + spin_lock(&online_lock); + --n_onlining_cpus_impl; + spin_unlock(&online_lock); +} + +static void init_onlining_cpus(void) +{ + spin_lock(&online_lock); + n_onlining_cpus_impl = num_online_cpus(); + spin_unlock(&online_lock); +} + +static int is_only_onlining_cpu(void) +{ + int result; + spin_lock(&online_lock); + result = n_onlining_cpus_impl == 1; + spin_unlock(&online_lock); + return result; +} + static int exynos4_check_enter(void) { unsigned int ret; @@ -840,7 +874,7 @@ static int exynos4_enter_idle(struct cpuidle_device *dev, spin_lock(&idle_lock); cpu_core |= (1 << cpu); - if ((cpu_core == 0x3) || (cpu_online(1) == 0)) { + if ((cpu_core == 0x3) || is_only_onlining_cpu()) { old_div = __raw_readl(EXYNOS4_CLKDIV_CPU); tmp = old_div; tmp |= ((0x7 << 28) | (0x7 << 0)); @@ -858,7 +892,7 @@ static int exynos4_enter_idle(struct cpuidle_device *dev, spin_lock(&idle_lock); - if ((cpu_core == 0x3) || (cpu_online(1) == 0)) { + if ((cpu_core == 0x3) || is_only_onlining_cpu()) { __raw_writel(old_div, EXYNOS4_CLKDIV_CPU); do { @@ -914,7 +948,12 @@ static int exynos4_enter_lowpower(struct cpuidle_device *dev, int ret; /* This mode only can be entered when only Core0 is online */ - if (num_online_cpus() != 1) { + if (use_clock_down == SW_CLK_DWN) { + enter_mode = is_only_onlining_cpu(); + } else { + enter_mode = num_online_cpus() == 1; + } + if (!enter_mode) { BUG_ON(!dev->safe_state); new_state = dev->safe_state; } @@ -975,6 +1014,31 @@ static struct notifier_block exynos4_cpuidle_notifier = { .notifier_call = exynos4_cpuidle_notifier_event, }; +static int exynos4_cpuidle_cpu_notifier_event(struct notifier_block *this, + unsigned long event, + void *hcpu) +{ + switch (event) { + case CPU_UP_PREPARE: + case CPU_UP_PREPARE_FROZEN: + add_onlininig_cpu(); + break; + + case CPU_UP_CANCELED: + case CPU_UP_CANCELED_FROZEN: + case CPU_DEAD: + case CPU_DEAD_FROZEN: + remove_onlininig_cpu(); + break; + } + + return NOTIFY_OK; +} + +static struct notifier_block exynos4_cpuidle_cpu_notifier = { + .notifier_call = exynos4_cpuidle_cpu_notifier_event, +}; + #ifdef CONFIG_EXYNOS4_ENABLE_CLOCK_DOWN static void __init exynos4_core_down_clk(void) { @@ -1147,7 +1211,16 @@ static int __init exynos4_init_cpuidle(void) return -EINVAL; } #endif + register_pm_notifier(&exynos4_cpuidle_notifier); + + if (use_clock_down == SW_CLK_DWN) { + get_online_cpus(); + init_onlining_cpus(); + register_cpu_notifier(&exynos4_cpuidle_cpu_notifier); + put_online_cpus(); + } + sys_pwr_conf_addr = (unsigned long)S5P_CENTRAL_SEQ_CONFIGURATION; /* Save register value for SCU */ -- cgit v1.1