1 /* 2 * Copyright (c) 2011-2014 Samsung Electronics Co., Ltd. 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> 8 * 9 * This program is free software; you can redistribute it and/or modify 10 * it under the terms of the GNU General Public License version 2 as 11 * published by the Free Software Foundation. 12 */ 13 14 #include <linux/cpuidle.h> 15 #include <linux/cpu_pm.h> 16 #include <linux/export.h> 17 #include <linux/module.h> 18 #include <linux/platform_device.h> 19 #include <linux/of.h> 20 #include <linux/platform_data/cpuidle-exynos.h> 21 22 #include <asm/proc-fns.h> 23 #include <asm/suspend.h> 24 #include <asm/cpuidle.h> 25 26 static atomic_t exynos_idle_barrier; 27 28 static struct cpuidle_exynos_data *exynos_cpuidle_pdata; 29 static void (*exynos_enter_aftr)(void); 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 62 static int exynos_enter_lowpower(struct cpuidle_device *dev, 63 struct cpuidle_driver *drv, 64 int index) 65 { 66 int new_index = index; 67 68 /* AFTR can only be entered when cores other than CPU0 are offline */ 69 if (num_online_cpus() > 1 || dev->cpu != 0) 70 new_index = drv->safe_state_index; 71 72 if (new_index == 0) 73 return arm_cpuidle_simple_enter(dev, drv, new_index); 74 75 exynos_enter_aftr(); 76 77 return new_index; 78 } 79 80 static struct cpuidle_driver exynos_idle_driver = { 81 .name = "exynos_idle", 82 .owner = THIS_MODULE, 83 .states = { 84 [0] = ARM_CPUIDLE_WFI_STATE, 85 [1] = { 86 .enter = exynos_enter_lowpower, 87 .exit_latency = 300, 88 .target_residency = 100000, 89 .name = "C1", 90 .desc = "ARM power down", 91 }, 92 }, 93 .state_count = 2, 94 .safe_state_index = 0, 95 }; 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 116 static int exynos_cpuidle_probe(struct platform_device *pdev) 117 { 118 int ret; 119 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 } 130 131 if (ret) { 132 dev_err(&pdev->dev, "failed to register cpuidle driver\n"); 133 return ret; 134 } 135 136 return 0; 137 } 138 139 static struct platform_driver exynos_cpuidle_driver = { 140 .probe = exynos_cpuidle_probe, 141 .driver = { 142 .name = "exynos_cpuidle", 143 }, 144 }; 145 146 module_platform_driver(exynos_cpuidle_driver); 147