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/suspend.h> 23#include <asm/cpuidle.h> 24 25static atomic_t exynos_idle_barrier; 26 27static struct cpuidle_exynos_data *exynos_cpuidle_pdata; 28static void (*exynos_enter_aftr)(void); 29 30static 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 61static 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 79static 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 96static 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 115static 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 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 139static struct platform_driver exynos_cpuidle_driver = { 140 .probe = exynos_cpuidle_probe, 141 .driver = { 142 .name = "exynos_cpuidle", 143 }, 144}; 145 146module_platform_driver(exynos_cpuidle_driver); 147