1// SPDX-License-Identifier: GPL-2.0-only
2/*
3 * Copyright (C) 2014 Freescale Semiconductor, Inc.
4 */
5
6#include <linux/cpuidle.h>
7#include <linux/cpu_pm.h>
8#include <linux/module.h>
9#include <asm/cacheflush.h>
10#include <asm/cpuidle.h>
11#include <asm/suspend.h>
12
13#include "common.h"
14#include "cpuidle.h"
15#include "hardware.h"
16
17static int imx6sx_idle_finish(unsigned long val)
18{
19	/*
20	 * for Cortex-A7 which has an internal L2
21	 * cache, need to flush it before powering
22	 * down ARM platform, since flushing L1 cache
23	 * here again has very small overhead, compared
24	 * to adding conditional code for L2 cache type,
25	 * just call flush_cache_all() is fine.
26	 */
27	flush_cache_all();
28	cpu_do_idle();
29
30	return 0;
31}
32
33static __cpuidle int imx6sx_enter_wait(struct cpuidle_device *dev,
34				       struct cpuidle_driver *drv, int index)
35{
36	imx6_set_lpm(WAIT_UNCLOCKED);
37
38	switch (index) {
39	case 1:
40		cpu_do_idle();
41		break;
42	case 2:
43		imx6_enable_rbc(true);
44		imx_gpc_set_arm_power_in_lpm(true);
45		imx_set_cpu_jump(0, v7_cpu_resume);
46		/* Need to notify there is a cpu pm operation. */
47		cpu_pm_enter();
48		cpu_cluster_pm_enter();
49
50		ct_cpuidle_enter();
51		cpu_suspend(0, imx6sx_idle_finish);
52		ct_cpuidle_exit();
53
54		cpu_cluster_pm_exit();
55		cpu_pm_exit();
56		imx_gpc_set_arm_power_in_lpm(false);
57		imx6_enable_rbc(false);
58		break;
59	default:
60		break;
61	}
62
63	imx6_set_lpm(WAIT_CLOCKED);
64
65	return index;
66}
67
68static struct cpuidle_driver imx6sx_cpuidle_driver = {
69	.name = "imx6sx_cpuidle",
70	.owner = THIS_MODULE,
71	.states = {
72		/* WFI */
73		ARM_CPUIDLE_WFI_STATE,
74		/* WAIT */
75		{
76			.exit_latency = 50,
77			.target_residency = 75,
78			.flags = CPUIDLE_FLAG_TIMER_STOP,
79			.enter = imx6sx_enter_wait,
80			.name = "WAIT",
81			.desc = "Clock off",
82		},
83		/* WAIT + ARM power off  */
84		{
85			/*
86			 * ARM gating 31us * 5 + RBC clear 65us
87			 * and some margin for SW execution, here set it
88			 * to 300us.
89			 */
90			.exit_latency = 300,
91			.target_residency = 500,
92			.flags = CPUIDLE_FLAG_TIMER_STOP |
93				 CPUIDLE_FLAG_RCU_IDLE,
94			.enter = imx6sx_enter_wait,
95			.name = "LOW-POWER-IDLE",
96			.desc = "ARM power off",
97		},
98	},
99	.state_count = 3,
100	.safe_state_index = 0,
101};
102
103int __init imx6sx_cpuidle_init(void)
104{
105	imx6_set_int_mem_clk_lpm(true);
106	imx6_enable_rbc(false);
107	imx_gpc_set_l2_mem_power_in_lpm(false);
108	/*
109	 * set ARM power up/down timing to the fastest,
110	 * sw2iso and sw can be set to one 32K cycle = 31us
111	 * except for power up sw2iso which need to be
112	 * larger than LDO ramp up time.
113	 */
114	imx_gpc_set_arm_power_up_timing(cpu_is_imx6sx() ? 0xf : 0x2, 1);
115	imx_gpc_set_arm_power_down_timing(1, 1);
116
117	return cpuidle_register(&imx6sx_cpuidle_driver, NULL);
118}
119