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 */
19 union 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 
do_service_request(union smc_message *msg)163 static 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 
190 static int boost_supported = 0;
191 static struct mutex cpufreq_mutex[MAX_PACKAGES];
192 
193 enum 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 */
203 static 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 
loongson3_cpufreq_get(unsigned int cpu)224 static 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 
loongson3_cpufreq_set(struct cpufreq_policy *policy, int freq_level)238 static 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  */
loongson3_cpufreq_target(struct cpufreq_policy *policy, unsigned int index)257 static 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 
loongson3_cpufreq_cpu_init(struct cpufreq_policy *policy)274 static 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 
loongson3_cpufreq_exit(struct cpufreq_policy *policy)288 static int loongson3_cpufreq_exit(struct cpufreq_policy *policy)
289 {
290 	return 0;
291 }
292 
293 static 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 
304 static struct platform_device_id cpufreq_id_table[] = {
305 	{ "loongson3_cpufreq", },
306 	{ /* sentinel */ }
307 };
308 
309 MODULE_DEVICE_TABLE(platform, cpufreq_id_table);
310 
311 static struct platform_driver cpufreq_driver = {
312 	.driver = {
313 		.name = "loongson3_cpufreq",
314 		.owner = THIS_MODULE,
315 	},
316 	.id_table = cpufreq_id_table,
317 };
318 
configure_cpufreq_info(void)319 static 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 
cpufreq_init(void)379 static 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 
403 err:
404 	platform_driver_unregister(&cpufreq_driver);
405 	return ret;
406 }
407 
cpufreq_exit(void)408 static void __exit cpufreq_exit(void)
409 {
410 	cpufreq_unregister_driver(&loongson3_cpufreq_driver);
411 	platform_driver_unregister(&cpufreq_driver);
412 }
413 
414 module_init(cpufreq_init);
415 module_exit(cpufreq_exit);
416 
417 MODULE_AUTHOR("Huacai Chen <chenhuacaic@loongson.cn>");
418 MODULE_DESCRIPTION("CPUFreq driver for Loongson-3 processors");
419 MODULE_LICENSE("GPL");
420