diff options
author | Bartlomiej Zolnierkiewicz <b.zolnierkie@samsung.com> | 2015-01-24 00:05:50 -0500 |
---|---|---|
committer | Kukjin Kim <kgene@kernel.org> | 2015-01-29 18:39:15 -0500 |
commit | 712eddf70225ab5ae65e946e22d2dfe6b93e8dd1 (patch) | |
tree | 62477a0a4b364cd2e87786481ee6dfe03e3fd569 | |
parent | 865e8b76a04d018f23d809ebf735c52105e3adb2 (diff) |
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>
-rw-r--r-- | arch/arm/mach-exynos/common.h | 4 | ||||
-rw-r--r-- | arch/arm/mach-exynos/exynos.c | 4 | ||||
-rw-r--r-- | arch/arm/mach-exynos/platsmp.c | 2 | ||||
-rw-r--r-- | arch/arm/mach-exynos/pm.c | 122 | ||||
-rw-r--r-- | drivers/cpuidle/Kconfig.arm | 1 | ||||
-rw-r--r-- | drivers/cpuidle/cpuidle-exynos.c | 76 | ||||
-rw-r--r-- | include/linux/platform_data/cpuidle-exynos.h | 20 |
7 files changed, 223 insertions, 6 deletions
diff --git a/arch/arm/mach-exynos/common.h b/arch/arm/mach-exynos/common.h index 865f878063cc..f70eca7ee705 100644 --- a/arch/arm/mach-exynos/common.h +++ b/arch/arm/mach-exynos/common.h | |||
@@ -13,6 +13,7 @@ | |||
13 | #define __ARCH_ARM_MACH_EXYNOS_COMMON_H | 13 | #define __ARCH_ARM_MACH_EXYNOS_COMMON_H |
14 | 14 | ||
15 | #include <linux/of.h> | 15 | #include <linux/of.h> |
16 | #include <linux/platform_data/cpuidle-exynos.h> | ||
16 | 17 | ||
17 | #define EXYNOS3250_SOC_ID 0xE3472000 | 18 | #define EXYNOS3250_SOC_ID 0xE3472000 |
18 | #define EXYNOS3_SOC_MASK 0xFFFFF000 | 19 | #define EXYNOS3_SOC_MASK 0xFFFFF000 |
@@ -150,8 +151,11 @@ extern void exynos_pm_central_suspend(void); | |||
150 | extern int exynos_pm_central_resume(void); | 151 | extern int exynos_pm_central_resume(void); |
151 | extern void exynos_enter_aftr(void); | 152 | extern void exynos_enter_aftr(void); |
152 | 153 | ||
154 | extern struct cpuidle_exynos_data cpuidle_coupled_exynos_data; | ||
155 | |||
153 | extern void s5p_init_cpu(void __iomem *cpuid_addr); | 156 | extern void s5p_init_cpu(void __iomem *cpuid_addr); |
154 | extern unsigned int samsung_rev(void); | 157 | extern unsigned int samsung_rev(void); |
158 | extern void __iomem *cpu_boot_reg_base(void); | ||
155 | 159 | ||
156 | static inline void pmu_raw_writel(u32 val, u32 offset) | 160 | static inline void pmu_raw_writel(u32 val, u32 offset) |
157 | { | 161 | { |
diff --git a/arch/arm/mach-exynos/exynos.c b/arch/arm/mach-exynos/exynos.c index c13d0837fa8c..509f2e5a2d70 100644 --- a/arch/arm/mach-exynos/exynos.c +++ b/arch/arm/mach-exynos/exynos.c | |||
@@ -246,6 +246,10 @@ static void __init exynos_dt_machine_init(void) | |||
246 | if (!IS_ENABLED(CONFIG_SMP)) | 246 | if (!IS_ENABLED(CONFIG_SMP)) |
247 | exynos_sysram_init(); | 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 | if (of_machine_is_compatible("samsung,exynos4210") || | 253 | if (of_machine_is_compatible("samsung,exynos4210") || |
250 | of_machine_is_compatible("samsung,exynos4212") || | 254 | of_machine_is_compatible("samsung,exynos4212") || |
251 | (of_machine_is_compatible("samsung,exynos4412") && | 255 | (of_machine_is_compatible("samsung,exynos4412") && |
diff --git a/arch/arm/mach-exynos/platsmp.c b/arch/arm/mach-exynos/platsmp.c index 7a1ebfeeeeb8..3f32c47a6d74 100644 --- a/arch/arm/mach-exynos/platsmp.c +++ b/arch/arm/mach-exynos/platsmp.c | |||
@@ -194,7 +194,7 @@ int exynos_cluster_power_state(int cluster) | |||
194 | S5P_CORE_LOCAL_PWR_EN); | 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 | if (soc_is_exynos4210() && samsung_rev() == EXYNOS4210_REV_1_1) | 199 | if (soc_is_exynos4210() && samsung_rev() == EXYNOS4210_REV_1_1) |
200 | return pmu_base_addr + S5P_INFORM5; | 200 | return pmu_base_addr + S5P_INFORM5; |
diff --git a/arch/arm/mach-exynos/pm.c b/arch/arm/mach-exynos/pm.c index 159eb4c9779e..1f9cbd434c60 100644 --- a/arch/arm/mach-exynos/pm.c +++ b/arch/arm/mach-exynos/pm.c | |||
@@ -179,3 +179,125 @@ void exynos_enter_aftr(void) | |||
179 | 179 | ||
180 | cpu_pm_exit(); | 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 | }; | ||
diff --git a/drivers/cpuidle/Kconfig.arm b/drivers/cpuidle/Kconfig.arm index 8c16ab20fb15..8e07c9419153 100644 --- a/drivers/cpuidle/Kconfig.arm +++ b/drivers/cpuidle/Kconfig.arm | |||
@@ -55,6 +55,7 @@ config ARM_AT91_CPUIDLE | |||
55 | config ARM_EXYNOS_CPUIDLE | 55 | config ARM_EXYNOS_CPUIDLE |
56 | bool "Cpu Idle Driver for the Exynos processors" | 56 | bool "Cpu Idle Driver for the Exynos processors" |
57 | depends on ARCH_EXYNOS | 57 | depends on ARCH_EXYNOS |
58 | select ARCH_NEEDS_CPU_IDLE_COUPLED if SMP | ||
58 | help | 59 | help |
59 | Select this to enable cpuidle for Exynos processors | 60 | Select this to enable cpuidle for Exynos processors |
60 | 61 | ||
diff --git a/drivers/cpuidle/cpuidle-exynos.c b/drivers/cpuidle/cpuidle-exynos.c index 4003a3160865..26f5f29fdb03 100644 --- a/drivers/cpuidle/cpuidle-exynos.c +++ b/drivers/cpuidle/cpuidle-exynos.c | |||
@@ -1,8 +1,11 @@ | |||
1 | /* linux/arch/arm/mach-exynos/cpuidle.c | 1 | /* |
2 | * | 2 | * Copyright (c) 2011-2014 Samsung Electronics Co., Ltd. |
3 | * Copyright (c) 2011 Samsung Electronics Co., Ltd. | ||
4 | * http://www.samsung.com | 3 | * http://www.samsung.com |
5 | * | 4 | * |
5 | * Coupled cpuidle support based on the work of: | ||
6 | * Colin Cross <ccross@android.com> | ||
7 | * Daniel Lezcano <daniel.lezcano@linaro.org> | ||
8 | * | ||
6 | * This program is free software; you can redistribute it and/or modify | 9 | * This program is free software; you can redistribute it and/or modify |
7 | * it under the terms of the GNU General Public License version 2 as | 10 | * it under the terms of the GNU General Public License version 2 as |
8 | * published by the Free Software Foundation. | 11 | * published by the Free Software Foundation. |
@@ -13,13 +16,49 @@ | |||
13 | #include <linux/export.h> | 16 | #include <linux/export.h> |
14 | #include <linux/module.h> | 17 | #include <linux/module.h> |
15 | #include <linux/platform_device.h> | 18 | #include <linux/platform_device.h> |
19 | #include <linux/of.h> | ||
20 | #include <linux/platform_data/cpuidle-exynos.h> | ||
16 | 21 | ||
17 | #include <asm/proc-fns.h> | 22 | #include <asm/proc-fns.h> |
18 | #include <asm/suspend.h> | 23 | #include <asm/suspend.h> |
19 | #include <asm/cpuidle.h> | 24 | #include <asm/cpuidle.h> |
20 | 25 | ||
26 | static atomic_t exynos_idle_barrier; | ||
27 | |||
28 | static struct cpuidle_exynos_data *exynos_cpuidle_pdata; | ||
21 | static void (*exynos_enter_aftr)(void); | 29 | static void (*exynos_enter_aftr)(void); |
22 | 30 | ||
31 | static int exynos_enter_coupled_lowpower(struct cpuidle_device *dev, | ||
32 | struct cpuidle_driver *drv, | ||
33 | int index) | ||
34 | { | ||
35 | int ret; | ||
36 | |||
37 | exynos_cpuidle_pdata->pre_enter_aftr(); | ||
38 | |||
39 | /* | ||
40 | * Waiting all cpus to reach this point at the same moment | ||
41 | */ | ||
42 | cpuidle_coupled_parallel_barrier(dev, &exynos_idle_barrier); | ||
43 | |||
44 | /* | ||
45 | * Both cpus will reach this point at the same time | ||
46 | */ | ||
47 | ret = dev->cpu ? exynos_cpuidle_pdata->cpu1_powerdown() | ||
48 | : exynos_cpuidle_pdata->cpu0_enter_aftr(); | ||
49 | if (ret) | ||
50 | index = ret; | ||
51 | |||
52 | /* | ||
53 | * Waiting all cpus to finish the power sequence before going further | ||
54 | */ | ||
55 | cpuidle_coupled_parallel_barrier(dev, &exynos_idle_barrier); | ||
56 | |||
57 | exynos_cpuidle_pdata->post_enter_aftr(); | ||
58 | |||
59 | return index; | ||
60 | } | ||
61 | |||
23 | static int exynos_enter_lowpower(struct cpuidle_device *dev, | 62 | static int exynos_enter_lowpower(struct cpuidle_device *dev, |
24 | struct cpuidle_driver *drv, | 63 | struct cpuidle_driver *drv, |
25 | int index) | 64 | int index) |
@@ -55,13 +94,40 @@ static struct cpuidle_driver exynos_idle_driver = { | |||
55 | .safe_state_index = 0, | 94 | .safe_state_index = 0, |
56 | }; | 95 | }; |
57 | 96 | ||
97 | static struct cpuidle_driver exynos_coupled_idle_driver = { | ||
98 | .name = "exynos_coupled_idle", | ||
99 | .owner = THIS_MODULE, | ||
100 | .states = { | ||
101 | [0] = ARM_CPUIDLE_WFI_STATE, | ||
102 | [1] = { | ||
103 | .enter = exynos_enter_coupled_lowpower, | ||
104 | .exit_latency = 5000, | ||
105 | .target_residency = 10000, | ||
106 | .flags = CPUIDLE_FLAG_COUPLED | | ||
107 | CPUIDLE_FLAG_TIMER_STOP, | ||
108 | .name = "C1", | ||
109 | .desc = "ARM power down", | ||
110 | }, | ||
111 | }, | ||
112 | .state_count = 2, | ||
113 | .safe_state_index = 0, | ||
114 | }; | ||
115 | |||
58 | static int exynos_cpuidle_probe(struct platform_device *pdev) | 116 | static int exynos_cpuidle_probe(struct platform_device *pdev) |
59 | { | 117 | { |
60 | int ret; | 118 | int ret; |
61 | 119 | ||
62 | exynos_enter_aftr = (void *)(pdev->dev.platform_data); | 120 | if (of_machine_is_compatible("samsung,exynos4210")) { |
121 | exynos_cpuidle_pdata = pdev->dev.platform_data; | ||
122 | |||
123 | ret = cpuidle_register(&exynos_coupled_idle_driver, | ||
124 | cpu_possible_mask); | ||
125 | } else { | ||
126 | exynos_enter_aftr = (void *)(pdev->dev.platform_data); | ||
127 | |||
128 | ret = cpuidle_register(&exynos_idle_driver, NULL); | ||
129 | } | ||
63 | 130 | ||
64 | ret = cpuidle_register(&exynos_idle_driver, NULL); | ||
65 | if (ret) { | 131 | if (ret) { |
66 | dev_err(&pdev->dev, "failed to register cpuidle driver\n"); | 132 | dev_err(&pdev->dev, "failed to register cpuidle driver\n"); |
67 | return ret; | 133 | return ret; |
diff --git a/include/linux/platform_data/cpuidle-exynos.h b/include/linux/platform_data/cpuidle-exynos.h new file mode 100644 index 000000000000..bfa40e4c5d5f --- /dev/null +++ b/include/linux/platform_data/cpuidle-exynos.h | |||
@@ -0,0 +1,20 @@ | |||
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 | ||