summaryrefslogtreecommitdiff
path: root/drivers/cpuidle/cpuidle-hisi.c
blob: 0036356396203bb3d87c2efeed1c6ebc69b3638f (plain)
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
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
/*
 * ARM64 generic CPU idle driver.
 *
 * Copyright (C) 2014 ARM Ltd.
 * Author: Lorenzo Pieralisi <lorenzo.pieralisi@arm.com>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 */

#define pr_fmt(fmt) "CPUidle arm64: " fmt

#include <linux/cpuidle.h>
#include <linux/cpumask.h>
#include <linux/cpu_pm.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/slab.h>

#include <asm/cpuidle.h>
#include <asm/suspend.h>
#include <linux/version.h>
#include <linux/sched.h>
#include <linux/cpu.h>
#include "dt_idle_states.h"
#ifdef CONFIG_HISI_CORESIGHT_TRACE
#include <linux/coresight.h>
#endif

enum {
	LITTLE_CLUSTER_ID = 0,
	BIG_CLUSTER_ID,
	MAX_CLUSTER_ID,
};

/*
 * hisi_enter_idle_state - Programs CPU to enter the specified state
 *
 * dev: cpuidle device
 * drv: cpuidle driver
 * idx: state index
 *
 * Called from the CPUidle framework to program the device to the
 * specified target state selected by the governor.
 */

extern int real_enable_cpuidle;

static int hisi_enter_idle_state(struct cpuidle_device *dev,
				     struct cpuidle_driver *drv, int idx)
{
	int ret;

	if (need_resched()) {
		return idx;
	}

	if (!idx) {
		cpu_do_idle();
		return idx;
	}

	ret = cpu_pm_enter();
	if (!ret) {
#ifdef CONFIG_ARCH_HISI
		local_fiq_disable();
#endif

		/*
		* Pass idle state index to cpu_suspend which in turn will
		* call the CPU ops suspend protocol with idle index as a
		* parameter.
		*/
		ret = arm_cpuidle_suspend(idx);

#ifdef CONFIG_HISI_CORESIGHT_TRACE
		/*Restore ETM registers */
		_etm4_cpuilde_restore();
#endif
#ifdef CONFIG_ARCH_HISI
		local_fiq_enable();
#endif
		cpu_pm_exit();

	}

	return ret ? -1 : idx;
}

static struct cpuidle_driver hisi_little_cluster_idle_driver = {
	.name = "hisi_little_cluster_idle",
	.owner = THIS_MODULE,
	/*
	* State at index 0 is standby wfi and considered standard
	* on all ARM platforms. If in some platforms simple wfi
	* can't be used as "state 0", DT bindings must be implemented
	* to work around this issue and allow installing a special
	* handler for idle state index 0.
	*/
	.states[0] = {
		.enter			= hisi_enter_idle_state,
		.exit_latency		= 1,
		.target_residency	= 1,
		.power_usage	      = UINT_MAX,
#if LINUX_VERSION_CODE < KERNEL_VERSION(4,1,14)
		.flags			= CPUIDLE_FLAG_TIME_VALID,
#endif
		.name			= "WFI",
		.desc			= "ARM64 WFI",
	}
};
static struct cpuidle_driver hisi_big_cluster_idle_driver = {
	.name = "hisi_big_cluster_idle",
	.owner = THIS_MODULE,
	/*
	* State at index 0 is standby wfi and considered standard
	* on all ARM platforms. If in some platforms simple wfi
	* can't be used as "state 0", DT bindings must be implemented
	* to work around this issue and allow installing a special
	* handler for idle state index 0.
	*/
	.states[0] = {
		.enter			= hisi_enter_idle_state,
		.exit_latency		= 1,
		.target_residency	= 1,
		.power_usage	      = UINT_MAX,
#if LINUX_VERSION_CODE < KERNEL_VERSION(4,1,14)
		.flags			= CPUIDLE_FLAG_TIME_VALID,
#endif
		.name			= "WFI",
		.desc			= "ARM64 WFI",
	}
};

static const struct of_device_id arm64_idle_state_match[] __initconst = {
	{ .compatible = "arm,idle-state",
	 .data = hisi_enter_idle_state },
	{ },
};

static int __init hisi_idle_drv_cpumask_init(struct cpuidle_driver *drv, int cluster_id)
{
	struct cpumask *cpumask;
	int cpu;

	cpumask = kzalloc(cpumask_size(), GFP_KERNEL);
	if (!cpumask)
		return -ENOMEM;

	for_each_possible_cpu(cpu) {
		if (cpu_topology[cpu].cluster_id == cluster_id)
			  cpumask_set_cpu(cpu, cpumask);
	}

	drv->cpumask = cpumask;

	return 0;
}

static void __init hisi_idle_drv_cpumask_uninit(struct cpuidle_driver *drv)
{
	kfree(drv->cpumask);
}

static int __init hisi_idle_drv_init(struct cpuidle_driver *drv)
{
	int cpu, ret;

	/*
	* Initialize idle states data, starting at index 1.
	* This driver is DT only, if no DT idle states are detected (ret == 0)
	* let the driver initialization fail accordingly since there is no
	* reason to initialize the idle driver if only wfi is supported.
	*/
	ret = dt_init_idle_driver(drv, arm64_idle_state_match, 1);
	if (ret <= 0) {
		if (ret)
			  pr_err("failed to initialize idle states\n");
		return ret ? : -ENODEV;
	}

	/*
	* Call arch CPU operations in order to initialize
	* idle states suspend back-end specific data
	*/
	for_each_possible_cpu(cpu) {
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4,1,14)
		ret = arm_cpuidle_init(cpu);
#else
		ret = cpu_init_idle(cpu);
#endif
		if (ret) {
			  pr_err("CPU %d failed to init idle CPU ops\n", cpu);
			  return ret;
		}
	}

	ret = cpuidle_register(drv, NULL);
	if (ret) {
		pr_err("failed to register cpuidle driver\n");
		return ret;
	}

	return 0;
}

static int __init hisi_multidrv_idle_init(struct cpuidle_driver *drv, int cluster_id)
{
	int ret;
	if (cluster_id >= MAX_CLUSTER_ID) {
		pr_err("cluster id is out of range.\n");
		return -ENODEV;
	}

	ret = hisi_idle_drv_cpumask_init(drv, cluster_id);
	if (ret) {
		pr_err("fail to init idle driver!\n");
		return ret;
	}

	ret = hisi_idle_drv_init(drv);
	if (ret) {
		hisi_idle_drv_cpumask_uninit(drv);
		pr_err("fail to register cluster%d cpuidle drv.\n", cluster_id);
		return ret;
	}

	return 0;
}


static int cpuidle_decoup_hotplug_notify(struct notifier_block *nb,
		unsigned long action, void *hcpu)
{
	if(action & CPU_TASKS_FROZEN)
		return NOTIFY_OK;
	switch (action & ~CPU_TASKS_FROZEN) {
	case CPU_UP_PREPARE:
	case CPU_DOWN_PREPARE:
		cpuidle_pause();
		break;
	case CPU_DOWN_FAILED:
	case CPU_UP_CANCELED:
	case CPU_ONLINE:
	case CPU_DEAD:
		cpuidle_resume();
		kick_all_cpus_sync();
		break;
	default:
		break;
	}
	return NOTIFY_OK;
}

static struct notifier_block cpuidle_decoup_hotplug_notifier = {
	.notifier_call = cpuidle_decoup_hotplug_notify,
};

/*
 * hisi_idle_init
 *
 * Registers the hisi multi cpuidle driver with the cpuidle
 * framework. It relies on core code to parse the idle states
 * and initialize them using driver data structures accordingly.
 */
static int __init hisi_idle_init(void)
{
	int ret;

	ret = hisi_multidrv_idle_init(&hisi_little_cluster_idle_driver, LITTLE_CLUSTER_ID);
	if (ret) {
		pr_err("fail to register little cluster cpuidle drv.\n");
		return ret;
	}

	ret = hisi_multidrv_idle_init(&hisi_big_cluster_idle_driver, BIG_CLUSTER_ID);
	if (ret) {
		pr_err("fail to register big cluster cpuidle drv.\n");
		return ret;
	}
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4,1,14)
	ret = register_cpu_notifier(&cpuidle_decoup_hotplug_notifier);
	if (ret) {
		pr_err("fail to register cpuidle_coupled_cpu_notifier.\n");
		return ret;
	}
#endif
	return 0;
}
device_initcall(hisi_idle_init);