1/*
2 * CPUFreq driver for the loongson-3 processors
3 *
4 * All revisions of Loongson-3 processor support this feature.
5 *
6 * Author: Huacai Chen <chenhuacai@loongson.cn>
7 * Copyright (C) 2020-2022 Loongson Technology Corporation Limited
8 */
9#include <linux/delay.h>
10#include <linux/module.h>
11#include <linux/cpufreq.h>
12#include <linux/platform_device.h>
13
14#include <asm/idle.h>
15#include <asm/loongarchregs.h>
16#include <loongson.h>
17
18/* Message */
19union smc_message
20{
21	u32 value;
22	struct
23	{
24		u32 id          : 4;
25		u32 info        : 4;
26		u32 val         : 16;
27		u32 cmd         : 6;
28		u32 extra       : 1;
29		u32 complete  	: 1;
30	};
31};
32
33/* Command return values */
34#define CMD_OK                      0  /* No error */
35#define CMD_ERROR                   1  /* Regular error */
36#define CMD_NOCMD                   2  /* Command does not support */
37#define CMD_INVAL                   3  /* Invalid Parameter */
38
39/* Version commands */
40/*
41 * CMD_GET_VERSION - Get interface version
42 * Input: none
43 * Output: version
44 */
45#define CMD_GET_VERSION             0x1
46
47/* Feature commands */
48/*
49 * CMD_GET_FEATURE - Get feature state
50 * Input: feature ID
51 * Output: feature flag
52 */
53#define CMD_GET_FEATURE             0x2
54
55/*
56 * CMD_SET_FEATURE - Set feature state
57 * Input: feature ID, feature flag
58 * output: none
59 */
60#define CMD_SET_FEATURE             0x3
61
62/* Feature IDs */
63#define FEATURE_SENSOR              0
64#define FEATURE_FAN                 1
65#define FEATURE_DVFS                2
66
67/* Sensor feature flags */
68#define FEATURE_SENSOR_ENABLE       (1 << 0)
69#define FEATURE_SENSOR_SAMPLE       (1 << 1)
70
71/* Fan feature flags */
72#define FEATURE_FAN_ENABLE          (1 << 0)
73#define FEATURE_FAN_AUTO            (1 << 1)
74
75/* DVFS feature flags */
76#define FEATURE_DVFS_ENABLE         (1 << 0)
77#define FEATURE_DVFS_BOOST          (1 << 1)
78
79/* Sensor commands */
80/*
81 * CMD_GET_SENSOR_NUM - Get number of sensors
82 * Input: none
83 * Output: number
84 */
85#define CMD_GET_SENSOR_NUM          0x4
86
87/*
88 * CMD_GET_SENSOR_STATUS - Get sensor status
89 * Input: sensor ID, type
90 * Output: sensor status
91 */
92#define CMD_GET_SENSOR_STATUS       0x5
93
94/* Sensor types */
95#define SENSOR_INFO_TYPE            0
96#define SENSOR_INFO_TYPE_TEMP       1
97
98/* Fan commands */
99/*
100 * CMD_GET_FAN_NUM - Get number of fans
101 * Input: none
102 * Output: number
103 */
104#define CMD_GET_FAN_NUM             0x6
105
106/*
107 * CMD_GET_FAN_INFO - Get fan status
108 * Input: fan ID, type
109 * Output: fan info
110 */
111#define CMD_GET_FAN_INFO            0x7
112
113/*
114 * CMD_SET_FAN_INFO - Set fan status
115 * Input: fan ID, type, value
116 * Output: none
117 */
118#define CMD_SET_FAN_INFO            0x8
119
120/* Fan types */
121#define FAN_INFO_TYPE_LEVEL         0
122
123/* DVFS commands */
124/*
125 * CMD_GET_FREQ_LEVEL_NUM - Get number of freq levels
126 * Input: none
127 * Output: number
128 */
129#define CMD_GET_FREQ_LEVEL_NUM      0x9
130
131/*
132 * CMD_GET_FREQ_BOOST_LEVEL - Get number of boost levels
133 * Input: none
134 * Output: number
135 */
136#define CMD_GET_FREQ_BOOST_LEVEL    0x10
137
138/*
139 * CMD_GET_FREQ_LEVEL_INFO - Get freq level info
140 * Input: level ID
141 * Output: level info
142 */
143#define CMD_GET_FREQ_LEVEL_INFO     0x11
144
145/*
146 * CMD_GET_FREQ_INFO - Get freq info
147 * Input: CPU ID, type
148 * Output: freq info
149 */
150#define CMD_GET_FREQ_INFO           0x12
151
152/*
153 * CMD_SET_FREQ_INFO - Set freq info
154 * Input: CPU ID, type, value
155 * Output: none
156 */
157#define CMD_SET_FREQ_INFO           0x13
158
159/* Freq types */
160#define FREQ_INFO_TYPE_FREQ         0
161#define FREQ_INFO_TYPE_LEVEL        1
162
163static inline int do_service_request(union smc_message *msg)
164{
165	int retries;
166	union smc_message last;
167
168	last.value = iocsr_read32(LOONGARCH_IOCSR_SMCMBX);
169	if (!last.complete)
170		return -1;
171
172	iocsr_write32(msg->value, LOONGARCH_IOCSR_SMCMBX);
173	iocsr_write32(iocsr_read32(LOONGARCH_IOCSR_MISC_FUNC) | IOCSR_MISC_FUNC_SOFT_INT,
174			LOONGARCH_IOCSR_MISC_FUNC);
175
176	for (retries = 0; retries < 10000; retries++) {
177		msg->value = iocsr_read32(LOONGARCH_IOCSR_SMCMBX);
178		if (msg->complete)
179			break;
180
181		usleep_range(4, 8);
182	}
183
184	if (!msg->complete || msg->cmd != CMD_OK)
185		return -1;
186
187	return 0;
188}
189
190static int boost_supported = 0;
191static struct mutex cpufreq_mutex[MAX_PACKAGES];
192
193enum freq {
194	FREQ_LEV0, /* Reserved */
195	FREQ_LEV1, FREQ_LEV2, FREQ_LEV3, FREQ_LEV4,
196	FREQ_LEV5, FREQ_LEV6, FREQ_LEV7, FREQ_LEV8,
197	FREQ_LEV9, FREQ_LEV10, FREQ_LEV11, FREQ_LEV12,
198	FREQ_LEV13, FREQ_LEV14, FREQ_LEV15, FREQ_LEV16,
199	FREQ_RESV
200};
201
202/* For Loongson-3A5000, support boost */
203static struct cpufreq_frequency_table loongson3_cpufreq_table[] = {
204	{0, FREQ_LEV0, CPUFREQ_ENTRY_INVALID},
205	{0, FREQ_LEV1, CPUFREQ_ENTRY_INVALID},
206	{0, FREQ_LEV2, CPUFREQ_ENTRY_INVALID},
207	{0, FREQ_LEV3, CPUFREQ_ENTRY_INVALID},
208	{0, FREQ_LEV4, CPUFREQ_ENTRY_INVALID},
209	{0, FREQ_LEV5, CPUFREQ_ENTRY_INVALID},
210	{0, FREQ_LEV6, CPUFREQ_ENTRY_INVALID},
211	{0, FREQ_LEV7, CPUFREQ_ENTRY_INVALID},
212	{0, FREQ_LEV8, CPUFREQ_ENTRY_INVALID},
213	{0, FREQ_LEV9, CPUFREQ_ENTRY_INVALID},
214	{0, FREQ_LEV10, CPUFREQ_ENTRY_INVALID},
215	{0, FREQ_LEV11, CPUFREQ_ENTRY_INVALID},
216	{0, FREQ_LEV12, CPUFREQ_ENTRY_INVALID},
217	{0, FREQ_LEV13, CPUFREQ_ENTRY_INVALID},
218	{0, FREQ_LEV14, CPUFREQ_ENTRY_INVALID},
219	{0, FREQ_LEV15, CPUFREQ_ENTRY_INVALID},
220	{0, FREQ_LEV16, CPUFREQ_ENTRY_INVALID},
221	{0, FREQ_RESV, CPUFREQ_TABLE_END},
222};
223
224static unsigned int loongson3_cpufreq_get(unsigned int cpu)
225{
226	union smc_message msg;
227
228	msg.id = cpu;
229	msg.info = FREQ_INFO_TYPE_FREQ;
230	msg.cmd = CMD_GET_FREQ_INFO;
231	msg.extra = 0;
232	msg.complete = 0;
233	do_service_request(&msg);
234
235	return msg.val * 1000;
236}
237
238static int loongson3_cpufreq_set(struct cpufreq_policy *policy, int freq_level)
239{
240	uint32_t core_id = cpu_data[policy->cpu].core;
241	union smc_message msg;
242
243	msg.id = core_id;
244	msg.info = FREQ_INFO_TYPE_LEVEL;
245	msg.val = freq_level;
246	msg.cmd = CMD_SET_FREQ_INFO;
247	msg.extra = 0;
248	msg.complete = 0;
249	do_service_request(&msg);
250
251	return 0;
252}
253
254/*
255 * Here we notify other drivers of the proposed change and the final change.
256 */
257static int loongson3_cpufreq_target(struct cpufreq_policy *policy,
258				     unsigned int index)
259{
260	unsigned int cpu = policy->cpu;
261	unsigned int package = cpu_data[cpu].package;
262
263	if (!cpu_online(cpu))
264		return -ENODEV;
265
266	/* setting the cpu frequency */
267	mutex_lock(&cpufreq_mutex[package]);
268	loongson3_cpufreq_set(policy, index);
269	mutex_unlock(&cpufreq_mutex[package]);
270
271	return 0;
272}
273
274static int loongson3_cpufreq_cpu_init(struct cpufreq_policy *policy)
275{
276	if (!cpu_online(policy->cpu))
277		return -ENODEV;
278
279	policy->cur = loongson3_cpufreq_get(policy->cpu);
280
281	policy->cpuinfo.transition_latency = 5000;
282	policy->freq_table = loongson3_cpufreq_table;
283	cpumask_copy(policy->cpus, topology_sibling_cpumask(policy->cpu));
284
285	return 0;
286}
287
288static int loongson3_cpufreq_exit(struct cpufreq_policy *policy)
289{
290	return 0;
291}
292
293static struct cpufreq_driver loongson3_cpufreq_driver = {
294	.name = "loongson3",
295	.flags = CPUFREQ_CONST_LOOPS,
296	.init = loongson3_cpufreq_cpu_init,
297	.verify = cpufreq_generic_frequency_table_verify,
298	.target_index = loongson3_cpufreq_target,
299	.get = loongson3_cpufreq_get,
300	.exit = loongson3_cpufreq_exit,
301	.attr = cpufreq_generic_attr,
302};
303
304static struct platform_device_id cpufreq_id_table[] = {
305	{ "loongson3_cpufreq", },
306	{ /* sentinel */ }
307};
308
309MODULE_DEVICE_TABLE(platform, cpufreq_id_table);
310
311static struct platform_driver cpufreq_driver = {
312	.driver = {
313		.name = "loongson3_cpufreq",
314		.owner = THIS_MODULE,
315	},
316	.id_table = cpufreq_id_table,
317};
318
319static int configure_cpufreq_info(void)
320{
321	int i, r, boost_level, max_level;
322	union smc_message msg;
323
324	if (!cpu_has_csr)
325		return -EPERM;
326
327	msg.cmd = CMD_GET_VERSION;
328	msg.extra = 0;
329	msg.complete = 0;
330	r = do_service_request(&msg);
331	if (r < 0 || msg.val < 0x1)
332		return -EPERM;
333
334	msg.id = FEATURE_DVFS;
335	msg.val = FEATURE_DVFS_ENABLE | FEATURE_DVFS_BOOST;
336	msg.cmd = CMD_SET_FEATURE;
337	msg.extra = 0;
338	msg.complete = 0;
339	r = do_service_request(&msg);
340	if (r < 0)
341		return -EPERM;
342
343	msg.cmd = CMD_GET_FREQ_LEVEL_NUM;
344	msg.extra = 0;
345	msg.complete = 0;
346	r = do_service_request(&msg);
347	if (r < 0)
348		return -EPERM;
349	max_level = msg.val;
350
351	msg.cmd = CMD_GET_FREQ_BOOST_LEVEL;
352	msg.extra = 0;
353	msg.complete = 0;
354	r = do_service_request(&msg);
355	if (r < 0)
356		return -EPERM;
357	boost_level = msg.val;
358	if (boost_level < max_level)
359		boost_supported = 1;
360
361	for (i = 0; i < max_level; i++) {
362		msg.cmd = CMD_GET_FREQ_LEVEL_INFO;
363		msg.id = i;
364		msg.info = FREQ_INFO_TYPE_FREQ;
365		msg.extra = 0;
366		msg.complete = 0;
367		r = do_service_request(&msg);
368		if (r < 0)
369			return -EPERM;
370
371		loongson3_cpufreq_table[i].frequency = msg.val * 1000;
372		if (i >= boost_level)
373			loongson3_cpufreq_table[i].flags = CPUFREQ_BOOST_FREQ;
374	}
375
376	return 0;
377}
378
379static int __init cpufreq_init(void)
380{
381	int i, ret;
382
383	ret = platform_driver_register(&cpufreq_driver);
384	if (ret)
385		goto err;
386
387	ret = configure_cpufreq_info();
388	if (ret)
389		goto err;
390
391	for (i = 0; i < MAX_PACKAGES; i++)
392		mutex_init(&cpufreq_mutex[i]);
393
394	ret = cpufreq_register_driver(&loongson3_cpufreq_driver);
395
396	if (boost_supported)
397		cpufreq_enable_boost_support();
398
399	pr_info("cpufreq: Loongson-3 CPU frequency driver.\n");
400
401	return ret;
402
403err:
404	platform_driver_unregister(&cpufreq_driver);
405	return ret;
406}
407
408static void __exit cpufreq_exit(void)
409{
410	cpufreq_unregister_driver(&loongson3_cpufreq_driver);
411	platform_driver_unregister(&cpufreq_driver);
412}
413
414module_init(cpufreq_init);
415module_exit(cpufreq_exit);
416
417MODULE_AUTHOR("Huacai Chen <chenhuacaic@loongson.cn>");
418MODULE_DESCRIPTION("CPUFreq driver for Loongson-3 processors");
419MODULE_LICENSE("GPL");
420