162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Copyright (c) 2011-2014 Samsung Electronics Co., Ltd.
462306a36Sopenharmony_ci *		http://www.samsung.com
562306a36Sopenharmony_ci *
662306a36Sopenharmony_ci * Coupled cpuidle support based on the work of:
762306a36Sopenharmony_ci *	Colin Cross <ccross@android.com>
862306a36Sopenharmony_ci *	Daniel Lezcano <daniel.lezcano@linaro.org>
962306a36Sopenharmony_ci*/
1062306a36Sopenharmony_ci
1162306a36Sopenharmony_ci#include <linux/cpuidle.h>
1262306a36Sopenharmony_ci#include <linux/cpu_pm.h>
1362306a36Sopenharmony_ci#include <linux/export.h>
1462306a36Sopenharmony_ci#include <linux/init.h>
1562306a36Sopenharmony_ci#include <linux/platform_device.h>
1662306a36Sopenharmony_ci#include <linux/of.h>
1762306a36Sopenharmony_ci#include <linux/platform_data/cpuidle-exynos.h>
1862306a36Sopenharmony_ci
1962306a36Sopenharmony_ci#include <asm/suspend.h>
2062306a36Sopenharmony_ci#include <asm/cpuidle.h>
2162306a36Sopenharmony_ci
2262306a36Sopenharmony_cistatic atomic_t exynos_idle_barrier;
2362306a36Sopenharmony_ci
2462306a36Sopenharmony_cistatic struct cpuidle_exynos_data *exynos_cpuidle_pdata;
2562306a36Sopenharmony_cistatic void (*exynos_enter_aftr)(void);
2662306a36Sopenharmony_ci
2762306a36Sopenharmony_cistatic int exynos_enter_coupled_lowpower(struct cpuidle_device *dev,
2862306a36Sopenharmony_ci					 struct cpuidle_driver *drv,
2962306a36Sopenharmony_ci					 int index)
3062306a36Sopenharmony_ci{
3162306a36Sopenharmony_ci	int ret;
3262306a36Sopenharmony_ci
3362306a36Sopenharmony_ci	exynos_cpuidle_pdata->pre_enter_aftr();
3462306a36Sopenharmony_ci
3562306a36Sopenharmony_ci	/*
3662306a36Sopenharmony_ci	 * Waiting all cpus to reach this point at the same moment
3762306a36Sopenharmony_ci	 */
3862306a36Sopenharmony_ci	cpuidle_coupled_parallel_barrier(dev, &exynos_idle_barrier);
3962306a36Sopenharmony_ci
4062306a36Sopenharmony_ci	/*
4162306a36Sopenharmony_ci	 * Both cpus will reach this point at the same time
4262306a36Sopenharmony_ci	 */
4362306a36Sopenharmony_ci	ret = dev->cpu ? exynos_cpuidle_pdata->cpu1_powerdown()
4462306a36Sopenharmony_ci		       : exynos_cpuidle_pdata->cpu0_enter_aftr();
4562306a36Sopenharmony_ci	if (ret)
4662306a36Sopenharmony_ci		index = ret;
4762306a36Sopenharmony_ci
4862306a36Sopenharmony_ci	/*
4962306a36Sopenharmony_ci	 * Waiting all cpus to finish the power sequence before going further
5062306a36Sopenharmony_ci	 */
5162306a36Sopenharmony_ci	cpuidle_coupled_parallel_barrier(dev, &exynos_idle_barrier);
5262306a36Sopenharmony_ci
5362306a36Sopenharmony_ci	exynos_cpuidle_pdata->post_enter_aftr();
5462306a36Sopenharmony_ci
5562306a36Sopenharmony_ci	return index;
5662306a36Sopenharmony_ci}
5762306a36Sopenharmony_ci
5862306a36Sopenharmony_cistatic int exynos_enter_lowpower(struct cpuidle_device *dev,
5962306a36Sopenharmony_ci				struct cpuidle_driver *drv,
6062306a36Sopenharmony_ci				int index)
6162306a36Sopenharmony_ci{
6262306a36Sopenharmony_ci	int new_index = index;
6362306a36Sopenharmony_ci
6462306a36Sopenharmony_ci	/* AFTR can only be entered when cores other than CPU0 are offline */
6562306a36Sopenharmony_ci	if (num_online_cpus() > 1 || dev->cpu != 0)
6662306a36Sopenharmony_ci		new_index = drv->safe_state_index;
6762306a36Sopenharmony_ci
6862306a36Sopenharmony_ci	if (new_index == 0)
6962306a36Sopenharmony_ci		return arm_cpuidle_simple_enter(dev, drv, new_index);
7062306a36Sopenharmony_ci
7162306a36Sopenharmony_ci	exynos_enter_aftr();
7262306a36Sopenharmony_ci
7362306a36Sopenharmony_ci	return new_index;
7462306a36Sopenharmony_ci}
7562306a36Sopenharmony_ci
7662306a36Sopenharmony_cistatic struct cpuidle_driver exynos_idle_driver = {
7762306a36Sopenharmony_ci	.name			= "exynos_idle",
7862306a36Sopenharmony_ci	.owner			= THIS_MODULE,
7962306a36Sopenharmony_ci	.states = {
8062306a36Sopenharmony_ci		[0] = ARM_CPUIDLE_WFI_STATE,
8162306a36Sopenharmony_ci		[1] = {
8262306a36Sopenharmony_ci			.enter			= exynos_enter_lowpower,
8362306a36Sopenharmony_ci			.exit_latency		= 300,
8462306a36Sopenharmony_ci			.target_residency	= 10000,
8562306a36Sopenharmony_ci			.name			= "C1",
8662306a36Sopenharmony_ci			.desc			= "ARM power down",
8762306a36Sopenharmony_ci		},
8862306a36Sopenharmony_ci	},
8962306a36Sopenharmony_ci	.state_count = 2,
9062306a36Sopenharmony_ci	.safe_state_index = 0,
9162306a36Sopenharmony_ci};
9262306a36Sopenharmony_ci
9362306a36Sopenharmony_cistatic struct cpuidle_driver exynos_coupled_idle_driver = {
9462306a36Sopenharmony_ci	.name			= "exynos_coupled_idle",
9562306a36Sopenharmony_ci	.owner			= THIS_MODULE,
9662306a36Sopenharmony_ci	.states = {
9762306a36Sopenharmony_ci		[0] = ARM_CPUIDLE_WFI_STATE,
9862306a36Sopenharmony_ci		[1] = {
9962306a36Sopenharmony_ci			.enter			= exynos_enter_coupled_lowpower,
10062306a36Sopenharmony_ci			.exit_latency		= 5000,
10162306a36Sopenharmony_ci			.target_residency	= 10000,
10262306a36Sopenharmony_ci			.flags			= CPUIDLE_FLAG_COUPLED |
10362306a36Sopenharmony_ci						  CPUIDLE_FLAG_TIMER_STOP,
10462306a36Sopenharmony_ci			.name			= "C1",
10562306a36Sopenharmony_ci			.desc			= "ARM power down",
10662306a36Sopenharmony_ci		},
10762306a36Sopenharmony_ci	},
10862306a36Sopenharmony_ci	.state_count = 2,
10962306a36Sopenharmony_ci	.safe_state_index = 0,
11062306a36Sopenharmony_ci};
11162306a36Sopenharmony_ci
11262306a36Sopenharmony_cistatic int exynos_cpuidle_probe(struct platform_device *pdev)
11362306a36Sopenharmony_ci{
11462306a36Sopenharmony_ci	int ret;
11562306a36Sopenharmony_ci
11662306a36Sopenharmony_ci	if (IS_ENABLED(CONFIG_SMP) &&
11762306a36Sopenharmony_ci	    (of_machine_is_compatible("samsung,exynos4210") ||
11862306a36Sopenharmony_ci	     of_machine_is_compatible("samsung,exynos3250"))) {
11962306a36Sopenharmony_ci		exynos_cpuidle_pdata = pdev->dev.platform_data;
12062306a36Sopenharmony_ci
12162306a36Sopenharmony_ci		ret = cpuidle_register(&exynos_coupled_idle_driver,
12262306a36Sopenharmony_ci				       cpu_possible_mask);
12362306a36Sopenharmony_ci	} else {
12462306a36Sopenharmony_ci		exynos_enter_aftr = (void *)(pdev->dev.platform_data);
12562306a36Sopenharmony_ci
12662306a36Sopenharmony_ci		ret = cpuidle_register(&exynos_idle_driver, NULL);
12762306a36Sopenharmony_ci	}
12862306a36Sopenharmony_ci
12962306a36Sopenharmony_ci	if (ret) {
13062306a36Sopenharmony_ci		dev_err(&pdev->dev, "failed to register cpuidle driver\n");
13162306a36Sopenharmony_ci		return ret;
13262306a36Sopenharmony_ci	}
13362306a36Sopenharmony_ci
13462306a36Sopenharmony_ci	return 0;
13562306a36Sopenharmony_ci}
13662306a36Sopenharmony_ci
13762306a36Sopenharmony_cistatic struct platform_driver exynos_cpuidle_driver = {
13862306a36Sopenharmony_ci	.probe	= exynos_cpuidle_probe,
13962306a36Sopenharmony_ci	.driver = {
14062306a36Sopenharmony_ci		.name = "exynos_cpuidle",
14162306a36Sopenharmony_ci	},
14262306a36Sopenharmony_ci};
14362306a36Sopenharmony_cibuiltin_platform_driver(exynos_cpuidle_driver);
144