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