Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

cpuidle: exynos: add coupled cpuidle support for exynos4210

The following patch adds coupled cpuidle support for Exynos4210 to
an existing cpuidle-exynos driver. As a result it enables AFTR mode
to be used by default on Exynos4210 without the need to hot unplug
CPU1 first.

The patch is heavily based on earlier cpuidle-exynos4210 driver from
Daniel Lezcano:

http://www.spinics.net/lists/linux-samsung-soc/msg28134.html

Changes from Daniel's code include:
- porting code to current kernels
- fixing it to work on my setup (by using S5P_INFORM register
instead of S5P_VA_SYSRAM one on Revison 1.1 and retrying poking
CPU1 out of the BOOT ROM if necessary)
- fixing rare lockup caused by waiting for CPU1 to get stuck in
the BOOT ROM (CPU hotplug code in arch/arm/mach-exynos/platsmp.c
doesn't require this and works fine)
- moving Exynos specific code to arch/arm/mach-exynos/pm.c
- using cpu_boot_reg_base() helper instead of BOOT_VECTOR macro
- using exynos_cpu_*() helpers instead of accessing registers
directly
- using arch_send_wakeup_ipi_mask() instead of dsb_sev()
(this matches CPU hotplug code in arch/arm/mach-exynos/platsmp.c)
- integrating separate exynos4210-cpuidle driver into existing
exynos-cpuidle one

Cc: Colin Cross <ccross@google.com>
Cc: Kukjin Kim <kgene.kim@samsung.com>
Cc: Krzysztof Kozlowski <k.kozlowski@samsung.com>
Cc: Tomasz Figa <tomasz.figa@gmail.com>
Signed-off-by: Bartlomiej Zolnierkiewicz <b.zolnierkie@samsung.com>
Signed-off-by: Daniel Lezcano <daniel.lezcano@linaro.org>
Acked-by: Kyungmin Park <kyungmin.park@samsung.com>
Signed-off-by: Kukjin Kim <kgene@kernel.org>

authored by

Bartlomiej Zolnierkiewicz and committed by
Kukjin Kim
712eddf7 865e8b76

+223 -6
+4
arch/arm/mach-exynos/common.h
··· 13 13 #define __ARCH_ARM_MACH_EXYNOS_COMMON_H 14 14 15 15 #include <linux/of.h> 16 + #include <linux/platform_data/cpuidle-exynos.h> 16 17 17 18 #define EXYNOS3250_SOC_ID 0xE3472000 18 19 #define EXYNOS3_SOC_MASK 0xFFFFF000 ··· 151 150 extern int exynos_pm_central_resume(void); 152 151 extern void exynos_enter_aftr(void); 153 152 153 + extern struct cpuidle_exynos_data cpuidle_coupled_exynos_data; 154 + 154 155 extern void s5p_init_cpu(void __iomem *cpuid_addr); 155 156 extern unsigned int samsung_rev(void); 157 + extern void __iomem *cpu_boot_reg_base(void); 156 158 157 159 static inline void pmu_raw_writel(u32 val, u32 offset) 158 160 {
+4
arch/arm/mach-exynos/exynos.c
··· 246 246 if (!IS_ENABLED(CONFIG_SMP)) 247 247 exynos_sysram_init(); 248 248 249 + #ifdef CONFIG_ARM_EXYNOS_CPUIDLE 250 + if (of_machine_is_compatible("samsung,exynos4210")) 251 + exynos_cpuidle.dev.platform_data = &cpuidle_coupled_exynos_data; 252 + #endif 249 253 if (of_machine_is_compatible("samsung,exynos4210") || 250 254 of_machine_is_compatible("samsung,exynos4212") || 251 255 (of_machine_is_compatible("samsung,exynos4412") &&
+1 -1
arch/arm/mach-exynos/platsmp.c
··· 194 194 S5P_CORE_LOCAL_PWR_EN); 195 195 } 196 196 197 - static inline void __iomem *cpu_boot_reg_base(void) 197 + void __iomem *cpu_boot_reg_base(void) 198 198 { 199 199 if (soc_is_exynos4210() && samsung_rev() == EXYNOS4210_REV_1_1) 200 200 return pmu_base_addr + S5P_INFORM5;
+122
arch/arm/mach-exynos/pm.c
··· 179 179 180 180 cpu_pm_exit(); 181 181 } 182 + 183 + static atomic_t cpu1_wakeup = ATOMIC_INIT(0); 184 + 185 + static int exynos_cpu0_enter_aftr(void) 186 + { 187 + int ret = -1; 188 + 189 + /* 190 + * If the other cpu is powered on, we have to power it off, because 191 + * the AFTR state won't work otherwise 192 + */ 193 + if (cpu_online(1)) { 194 + /* 195 + * We reach a sync point with the coupled idle state, we know 196 + * the other cpu will power down itself or will abort the 197 + * sequence, let's wait for one of these to happen 198 + */ 199 + while (exynos_cpu_power_state(1)) { 200 + /* 201 + * The other cpu may skip idle and boot back 202 + * up again 203 + */ 204 + if (atomic_read(&cpu1_wakeup)) 205 + goto abort; 206 + 207 + /* 208 + * The other cpu may bounce through idle and 209 + * boot back up again, getting stuck in the 210 + * boot rom code 211 + */ 212 + if (__raw_readl(cpu_boot_reg_base()) == 0) 213 + goto abort; 214 + 215 + cpu_relax(); 216 + } 217 + } 218 + 219 + exynos_enter_aftr(); 220 + ret = 0; 221 + 222 + abort: 223 + if (cpu_online(1)) { 224 + /* 225 + * Set the boot vector to something non-zero 226 + */ 227 + __raw_writel(virt_to_phys(exynos_cpu_resume), 228 + cpu_boot_reg_base()); 229 + dsb(); 230 + 231 + /* 232 + * Turn on cpu1 and wait for it to be on 233 + */ 234 + exynos_cpu_power_up(1); 235 + while (exynos_cpu_power_state(1) != S5P_CORE_LOCAL_PWR_EN) 236 + cpu_relax(); 237 + 238 + while (!atomic_read(&cpu1_wakeup)) { 239 + /* 240 + * Poke cpu1 out of the boot rom 241 + */ 242 + __raw_writel(virt_to_phys(exynos_cpu_resume), 243 + cpu_boot_reg_base()); 244 + 245 + arch_send_wakeup_ipi_mask(cpumask_of(1)); 246 + } 247 + } 248 + 249 + return ret; 250 + } 251 + 252 + static int exynos_wfi_finisher(unsigned long flags) 253 + { 254 + cpu_do_idle(); 255 + 256 + return -1; 257 + } 258 + 259 + static int exynos_cpu1_powerdown(void) 260 + { 261 + int ret = -1; 262 + 263 + /* 264 + * Idle sequence for cpu1 265 + */ 266 + if (cpu_pm_enter()) 267 + goto cpu1_aborted; 268 + 269 + /* 270 + * Turn off cpu 1 271 + */ 272 + exynos_cpu_power_down(1); 273 + 274 + ret = cpu_suspend(0, exynos_wfi_finisher); 275 + 276 + cpu_pm_exit(); 277 + 278 + cpu1_aborted: 279 + dsb(); 280 + /* 281 + * Notify cpu 0 that cpu 1 is awake 282 + */ 283 + atomic_set(&cpu1_wakeup, 1); 284 + 285 + return ret; 286 + } 287 + 288 + static void exynos_pre_enter_aftr(void) 289 + { 290 + __raw_writel(virt_to_phys(exynos_cpu_resume), cpu_boot_reg_base()); 291 + } 292 + 293 + static void exynos_post_enter_aftr(void) 294 + { 295 + atomic_set(&cpu1_wakeup, 0); 296 + } 297 + 298 + struct cpuidle_exynos_data cpuidle_coupled_exynos_data = { 299 + .cpu0_enter_aftr = exynos_cpu0_enter_aftr, 300 + .cpu1_powerdown = exynos_cpu1_powerdown, 301 + .pre_enter_aftr = exynos_pre_enter_aftr, 302 + .post_enter_aftr = exynos_post_enter_aftr, 303 + };
+1
drivers/cpuidle/Kconfig.arm
··· 55 55 config ARM_EXYNOS_CPUIDLE 56 56 bool "Cpu Idle Driver for the Exynos processors" 57 57 depends on ARCH_EXYNOS 58 + select ARCH_NEEDS_CPU_IDLE_COUPLED if SMP 58 59 help 59 60 Select this to enable cpuidle for Exynos processors 60 61
+71 -5
drivers/cpuidle/cpuidle-exynos.c
··· 1 - /* linux/arch/arm/mach-exynos/cpuidle.c 2 - * 3 - * Copyright (c) 2011 Samsung Electronics Co., Ltd. 1 + /* 2 + * Copyright (c) 2011-2014 Samsung Electronics Co., Ltd. 4 3 * http://www.samsung.com 4 + * 5 + * Coupled cpuidle support based on the work of: 6 + * Colin Cross <ccross@android.com> 7 + * Daniel Lezcano <daniel.lezcano@linaro.org> 5 8 * 6 9 * This program is free software; you can redistribute it and/or modify 7 10 * it under the terms of the GNU General Public License version 2 as ··· 16 13 #include <linux/export.h> 17 14 #include <linux/module.h> 18 15 #include <linux/platform_device.h> 16 + #include <linux/of.h> 17 + #include <linux/platform_data/cpuidle-exynos.h> 19 18 20 19 #include <asm/proc-fns.h> 21 20 #include <asm/suspend.h> 22 21 #include <asm/cpuidle.h> 23 22 23 + static atomic_t exynos_idle_barrier; 24 + 25 + static struct cpuidle_exynos_data *exynos_cpuidle_pdata; 24 26 static void (*exynos_enter_aftr)(void); 27 + 28 + static int exynos_enter_coupled_lowpower(struct cpuidle_device *dev, 29 + struct cpuidle_driver *drv, 30 + int index) 31 + { 32 + int ret; 33 + 34 + exynos_cpuidle_pdata->pre_enter_aftr(); 35 + 36 + /* 37 + * Waiting all cpus to reach this point at the same moment 38 + */ 39 + cpuidle_coupled_parallel_barrier(dev, &exynos_idle_barrier); 40 + 41 + /* 42 + * Both cpus will reach this point at the same time 43 + */ 44 + ret = dev->cpu ? exynos_cpuidle_pdata->cpu1_powerdown() 45 + : exynos_cpuidle_pdata->cpu0_enter_aftr(); 46 + if (ret) 47 + index = ret; 48 + 49 + /* 50 + * Waiting all cpus to finish the power sequence before going further 51 + */ 52 + cpuidle_coupled_parallel_barrier(dev, &exynos_idle_barrier); 53 + 54 + exynos_cpuidle_pdata->post_enter_aftr(); 55 + 56 + return index; 57 + } 25 58 26 59 static int exynos_enter_lowpower(struct cpuidle_device *dev, 27 60 struct cpuidle_driver *drv, ··· 94 55 .safe_state_index = 0, 95 56 }; 96 57 58 + static struct cpuidle_driver exynos_coupled_idle_driver = { 59 + .name = "exynos_coupled_idle", 60 + .owner = THIS_MODULE, 61 + .states = { 62 + [0] = ARM_CPUIDLE_WFI_STATE, 63 + [1] = { 64 + .enter = exynos_enter_coupled_lowpower, 65 + .exit_latency = 5000, 66 + .target_residency = 10000, 67 + .flags = CPUIDLE_FLAG_COUPLED | 68 + CPUIDLE_FLAG_TIMER_STOP, 69 + .name = "C1", 70 + .desc = "ARM power down", 71 + }, 72 + }, 73 + .state_count = 2, 74 + .safe_state_index = 0, 75 + }; 76 + 97 77 static int exynos_cpuidle_probe(struct platform_device *pdev) 98 78 { 99 79 int ret; 100 80 101 - exynos_enter_aftr = (void *)(pdev->dev.platform_data); 81 + if (of_machine_is_compatible("samsung,exynos4210")) { 82 + exynos_cpuidle_pdata = pdev->dev.platform_data; 102 83 103 - ret = cpuidle_register(&exynos_idle_driver, NULL); 84 + ret = cpuidle_register(&exynos_coupled_idle_driver, 85 + cpu_possible_mask); 86 + } else { 87 + exynos_enter_aftr = (void *)(pdev->dev.platform_data); 88 + 89 + ret = cpuidle_register(&exynos_idle_driver, NULL); 90 + } 91 + 104 92 if (ret) { 105 93 dev_err(&pdev->dev, "failed to register cpuidle driver\n"); 106 94 return ret;
+20
include/linux/platform_data/cpuidle-exynos.h
··· 1 + /* 2 + * Copyright (c) 2014 Samsung Electronics Co., Ltd. 3 + * http://www.samsung.com 4 + * 5 + * This program is free software; you can redistribute it and/or modify 6 + * it under the terms of the GNU General Public License version 2 as 7 + * published by the Free Software Foundation. 8 + */ 9 + 10 + #ifndef __CPUIDLE_EXYNOS_H 11 + #define __CPUIDLE_EXYNOS_H 12 + 13 + struct cpuidle_exynos_data { 14 + int (*cpu0_enter_aftr)(void); 15 + int (*cpu1_powerdown)(void); 16 + void (*pre_enter_aftr)(void); 17 + void (*post_enter_aftr)(void); 18 + }; 19 + 20 + #endif