162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Copyright (C) 2014 Freescale Semiconductor, Inc.
462306a36Sopenharmony_ci */
562306a36Sopenharmony_ci
662306a36Sopenharmony_ci#include <linux/cpuidle.h>
762306a36Sopenharmony_ci#include <linux/cpu_pm.h>
862306a36Sopenharmony_ci#include <linux/module.h>
962306a36Sopenharmony_ci#include <asm/cacheflush.h>
1062306a36Sopenharmony_ci#include <asm/cpuidle.h>
1162306a36Sopenharmony_ci#include <asm/suspend.h>
1262306a36Sopenharmony_ci
1362306a36Sopenharmony_ci#include "common.h"
1462306a36Sopenharmony_ci#include "cpuidle.h"
1562306a36Sopenharmony_ci#include "hardware.h"
1662306a36Sopenharmony_ci
1762306a36Sopenharmony_cistatic int imx6sx_idle_finish(unsigned long val)
1862306a36Sopenharmony_ci{
1962306a36Sopenharmony_ci	/*
2062306a36Sopenharmony_ci	 * for Cortex-A7 which has an internal L2
2162306a36Sopenharmony_ci	 * cache, need to flush it before powering
2262306a36Sopenharmony_ci	 * down ARM platform, since flushing L1 cache
2362306a36Sopenharmony_ci	 * here again has very small overhead, compared
2462306a36Sopenharmony_ci	 * to adding conditional code for L2 cache type,
2562306a36Sopenharmony_ci	 * just call flush_cache_all() is fine.
2662306a36Sopenharmony_ci	 */
2762306a36Sopenharmony_ci	flush_cache_all();
2862306a36Sopenharmony_ci	cpu_do_idle();
2962306a36Sopenharmony_ci
3062306a36Sopenharmony_ci	return 0;
3162306a36Sopenharmony_ci}
3262306a36Sopenharmony_ci
3362306a36Sopenharmony_cistatic __cpuidle int imx6sx_enter_wait(struct cpuidle_device *dev,
3462306a36Sopenharmony_ci				       struct cpuidle_driver *drv, int index)
3562306a36Sopenharmony_ci{
3662306a36Sopenharmony_ci	imx6_set_lpm(WAIT_UNCLOCKED);
3762306a36Sopenharmony_ci
3862306a36Sopenharmony_ci	switch (index) {
3962306a36Sopenharmony_ci	case 1:
4062306a36Sopenharmony_ci		cpu_do_idle();
4162306a36Sopenharmony_ci		break;
4262306a36Sopenharmony_ci	case 2:
4362306a36Sopenharmony_ci		imx6_enable_rbc(true);
4462306a36Sopenharmony_ci		imx_gpc_set_arm_power_in_lpm(true);
4562306a36Sopenharmony_ci		imx_set_cpu_jump(0, v7_cpu_resume);
4662306a36Sopenharmony_ci		/* Need to notify there is a cpu pm operation. */
4762306a36Sopenharmony_ci		cpu_pm_enter();
4862306a36Sopenharmony_ci		cpu_cluster_pm_enter();
4962306a36Sopenharmony_ci
5062306a36Sopenharmony_ci		ct_cpuidle_enter();
5162306a36Sopenharmony_ci		cpu_suspend(0, imx6sx_idle_finish);
5262306a36Sopenharmony_ci		ct_cpuidle_exit();
5362306a36Sopenharmony_ci
5462306a36Sopenharmony_ci		cpu_cluster_pm_exit();
5562306a36Sopenharmony_ci		cpu_pm_exit();
5662306a36Sopenharmony_ci		imx_gpc_set_arm_power_in_lpm(false);
5762306a36Sopenharmony_ci		imx6_enable_rbc(false);
5862306a36Sopenharmony_ci		break;
5962306a36Sopenharmony_ci	default:
6062306a36Sopenharmony_ci		break;
6162306a36Sopenharmony_ci	}
6262306a36Sopenharmony_ci
6362306a36Sopenharmony_ci	imx6_set_lpm(WAIT_CLOCKED);
6462306a36Sopenharmony_ci
6562306a36Sopenharmony_ci	return index;
6662306a36Sopenharmony_ci}
6762306a36Sopenharmony_ci
6862306a36Sopenharmony_cistatic struct cpuidle_driver imx6sx_cpuidle_driver = {
6962306a36Sopenharmony_ci	.name = "imx6sx_cpuidle",
7062306a36Sopenharmony_ci	.owner = THIS_MODULE,
7162306a36Sopenharmony_ci	.states = {
7262306a36Sopenharmony_ci		/* WFI */
7362306a36Sopenharmony_ci		ARM_CPUIDLE_WFI_STATE,
7462306a36Sopenharmony_ci		/* WAIT */
7562306a36Sopenharmony_ci		{
7662306a36Sopenharmony_ci			.exit_latency = 50,
7762306a36Sopenharmony_ci			.target_residency = 75,
7862306a36Sopenharmony_ci			.flags = CPUIDLE_FLAG_TIMER_STOP,
7962306a36Sopenharmony_ci			.enter = imx6sx_enter_wait,
8062306a36Sopenharmony_ci			.name = "WAIT",
8162306a36Sopenharmony_ci			.desc = "Clock off",
8262306a36Sopenharmony_ci		},
8362306a36Sopenharmony_ci		/* WAIT + ARM power off  */
8462306a36Sopenharmony_ci		{
8562306a36Sopenharmony_ci			/*
8662306a36Sopenharmony_ci			 * ARM gating 31us * 5 + RBC clear 65us
8762306a36Sopenharmony_ci			 * and some margin for SW execution, here set it
8862306a36Sopenharmony_ci			 * to 300us.
8962306a36Sopenharmony_ci			 */
9062306a36Sopenharmony_ci			.exit_latency = 300,
9162306a36Sopenharmony_ci			.target_residency = 500,
9262306a36Sopenharmony_ci			.flags = CPUIDLE_FLAG_TIMER_STOP |
9362306a36Sopenharmony_ci				 CPUIDLE_FLAG_RCU_IDLE,
9462306a36Sopenharmony_ci			.enter = imx6sx_enter_wait,
9562306a36Sopenharmony_ci			.name = "LOW-POWER-IDLE",
9662306a36Sopenharmony_ci			.desc = "ARM power off",
9762306a36Sopenharmony_ci		},
9862306a36Sopenharmony_ci	},
9962306a36Sopenharmony_ci	.state_count = 3,
10062306a36Sopenharmony_ci	.safe_state_index = 0,
10162306a36Sopenharmony_ci};
10262306a36Sopenharmony_ci
10362306a36Sopenharmony_ciint __init imx6sx_cpuidle_init(void)
10462306a36Sopenharmony_ci{
10562306a36Sopenharmony_ci	imx6_set_int_mem_clk_lpm(true);
10662306a36Sopenharmony_ci	imx6_enable_rbc(false);
10762306a36Sopenharmony_ci	imx_gpc_set_l2_mem_power_in_lpm(false);
10862306a36Sopenharmony_ci	/*
10962306a36Sopenharmony_ci	 * set ARM power up/down timing to the fastest,
11062306a36Sopenharmony_ci	 * sw2iso and sw can be set to one 32K cycle = 31us
11162306a36Sopenharmony_ci	 * except for power up sw2iso which need to be
11262306a36Sopenharmony_ci	 * larger than LDO ramp up time.
11362306a36Sopenharmony_ci	 */
11462306a36Sopenharmony_ci	imx_gpc_set_arm_power_up_timing(cpu_is_imx6sx() ? 0xf : 0x2, 1);
11562306a36Sopenharmony_ci	imx_gpc_set_arm_power_down_timing(1, 1);
11662306a36Sopenharmony_ci
11762306a36Sopenharmony_ci	return cpuidle_register(&imx6sx_cpuidle_driver, NULL);
11862306a36Sopenharmony_ci}
119