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/init.h> 18 #include <linux/platform_device.h> 19 #include <linux/of.h> 20 #include <linux/platform_data/cpuidle-exynos.h> 21 22 #include <asm/suspend.h> 23 #include <asm/cpuidle.h> 24 25 static atomic_t exynos_idle_barrier; 26 27 static struct cpuidle_exynos_data *exynos_cpuidle_pdata; 28 static void (*exynos_enter_aftr)(void); 29 30 static int exynos_enter_coupled_lowpower(struct cpuidle_device *dev, 31 struct cpuidle_driver *drv, 32 int index) 33 { 34 int ret; 35 36 exynos_cpuidle_pdata->pre_enter_aftr(); 37 38 /* 39 * Waiting all cpus to reach this point at the same moment 40 */ 41 cpuidle_coupled_parallel_barrier(dev, &exynos_idle_barrier); 42 43 /* 44 * Both cpus will reach this point at the same time 45 */ 46 ret = dev->cpu ? exynos_cpuidle_pdata->cpu1_powerdown() 47 : exynos_cpuidle_pdata->cpu0_enter_aftr(); 48 if (ret) 49 index = ret; 50 51 /* 52 * Waiting all cpus to finish the power sequence before going further 53 */ 54 cpuidle_coupled_parallel_barrier(dev, &exynos_idle_barrier); 55 56 exynos_cpuidle_pdata->post_enter_aftr(); 57 58 return index; 59 } 60 61 static int exynos_enter_lowpower(struct cpuidle_device *dev, 62 struct cpuidle_driver *drv, 63 int index) 64 { 65 int new_index = index; 66 67 /* AFTR can only be entered when cores other than CPU0 are offline */ 68 if (num_online_cpus() > 1 || dev->cpu != 0) 69 new_index = drv->safe_state_index; 70 71 if (new_index == 0) 72 return arm_cpuidle_simple_enter(dev, drv, new_index); 73 74 exynos_enter_aftr(); 75 76 return new_index; 77 } 78 79 static struct cpuidle_driver exynos_idle_driver = { 80 .name = "exynos_idle", 81 .owner = THIS_MODULE, 82 .states = { 83 [0] = ARM_CPUIDLE_WFI_STATE, 84 [1] = { 85 .enter = exynos_enter_lowpower, 86 .exit_latency = 300, 87 .target_residency = 100000, 88 .name = "C1", 89 .desc = "ARM power down", 90 }, 91 }, 92 .state_count = 2, 93 .safe_state_index = 0, 94 }; 95 96 static struct cpuidle_driver exynos_coupled_idle_driver = { 97 .name = "exynos_coupled_idle", 98 .owner = THIS_MODULE, 99 .states = { 100 [0] = ARM_CPUIDLE_WFI_STATE, 101 [1] = { 102 .enter = exynos_enter_coupled_lowpower, 103 .exit_latency = 5000, 104 .target_residency = 10000, 105 .flags = CPUIDLE_FLAG_COUPLED | 106 CPUIDLE_FLAG_TIMER_STOP, 107 .name = "C1", 108 .desc = "ARM power down", 109 }, 110 }, 111 .state_count = 2, 112 .safe_state_index = 0, 113 }; 114 115 static int exynos_cpuidle_probe(struct platform_device *pdev) 116 { 117 int ret; 118 119 if (IS_ENABLED(CONFIG_SMP) && 120 (of_machine_is_compatible("samsung,exynos4210") || 121 of_machine_is_compatible("samsung,exynos3250"))) { 122 exynos_cpuidle_pdata = pdev->dev.platform_data; 123 124 ret = cpuidle_register(&exynos_coupled_idle_driver, 125 cpu_possible_mask); 126 } else { 127 exynos_enter_aftr = (void *)(pdev->dev.platform_data); 128 129 ret = cpuidle_register(&exynos_idle_driver, NULL); 130 } 131 132 if (ret) { 133 dev_err(&pdev->dev, "failed to register cpuidle driver\n"); 134 return ret; 135 } 136 137 return 0; 138 } 139 140 static struct platform_driver exynos_cpuidle_driver = { 141 .probe = exynos_cpuidle_probe, 142 .driver = { 143 .name = "exynos_cpuidle", 144 }, 145 }; 146 builtin_platform_driver(exynos_cpuidle_driver); 147