1 // SPDX-License-Identifier: GPL-2.0-only
3 * CPUFreq driver for the Loongson-3 processors.
5 * All revisions of Loongson-3 processor support cpu_has_scalefreq feature.
7 * Author: Huacai Chen <chenhuacai@loongson.cn>
8 * Copyright (C) 2024 Loongson Technology Corporation Limited
10 #include <linux/cpufreq.h>
11 #include <linux/delay.h>
12 #include <linux/module.h>
13 #include <linux/platform_device.h>
14 #include <linux/units.h>
17 #include <asm/loongarch.h>
18 #include <asm/loongson.h>
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 */
39 /* Version commands */
41 * CMD_GET_VERSION - Get interface version
45 #define CMD_GET_VERSION 0x1
47 /* Feature commands */
49 * CMD_GET_FEATURE - Get feature state
51 * Output: feature flag
53 #define CMD_GET_FEATURE 0x2
56 * CMD_SET_FEATURE - Set feature state
57 * Input: feature ID, feature flag
60 #define CMD_SET_FEATURE 0x3
63 #define FEATURE_SENSOR 0
65 #define FEATURE_DVFS 2
67 /* Sensor feature flags */
68 #define FEATURE_SENSOR_ENABLE BIT(0)
69 #define FEATURE_SENSOR_SAMPLE BIT(1)
71 /* Fan feature flags */
72 #define FEATURE_FAN_ENABLE BIT(0)
73 #define FEATURE_FAN_AUTO BIT(1)
75 /* DVFS feature flags */
76 #define FEATURE_DVFS_ENABLE BIT(0)
77 #define FEATURE_DVFS_BOOST BIT(1)
78 #define FEATURE_DVFS_AUTO BIT(2)
79 #define FEATURE_DVFS_SINGLE_BOOST BIT(3)
83 * CMD_GET_SENSOR_NUM - Get number of sensors
87 #define CMD_GET_SENSOR_NUM 0x4
90 * CMD_GET_SENSOR_STATUS - Get sensor status
91 * Input: sensor ID, type
92 * Output: sensor status
94 #define CMD_GET_SENSOR_STATUS 0x5
97 #define SENSOR_INFO_TYPE 0
98 #define SENSOR_INFO_TYPE_TEMP 1
102 * CMD_GET_FAN_NUM - Get number of fans
106 #define CMD_GET_FAN_NUM 0x6
109 * CMD_GET_FAN_INFO - Get fan status
110 * Input: fan ID, type
113 #define CMD_GET_FAN_INFO 0x7
116 * CMD_SET_FAN_INFO - Set fan status
117 * Input: fan ID, type, value
120 #define CMD_SET_FAN_INFO 0x8
123 #define FAN_INFO_TYPE_LEVEL 0
127 * CMD_GET_FREQ_LEVEL_NUM - Get number of freq levels
131 #define CMD_GET_FREQ_LEVEL_NUM 0x9
134 * CMD_GET_FREQ_BOOST_LEVEL - Get the first boost level
138 #define CMD_GET_FREQ_BOOST_LEVEL 0x10
141 * CMD_GET_FREQ_LEVEL_INFO - Get freq level info
142 * Input: CPU ID, level ID
145 #define CMD_GET_FREQ_LEVEL_INFO 0x11
148 * CMD_GET_FREQ_INFO - Get freq info
149 * Input: CPU ID, type
152 #define CMD_GET_FREQ_INFO 0x12
155 * CMD_SET_FREQ_INFO - Set freq info
156 * Input: CPU ID, type, value
159 #define CMD_SET_FREQ_INFO 0x13
162 #define FREQ_INFO_TYPE_FREQ 0
163 #define FREQ_INFO_TYPE_LEVEL 1
165 #define FREQ_MAX_LEVEL 16
167 struct loongson3_freq_data
{
168 unsigned int def_freq_level
;
169 struct cpufreq_frequency_table table
[];
172 static struct mutex cpufreq_mutex
[MAX_PACKAGES
];
173 static struct cpufreq_driver loongson3_cpufreq_driver
;
174 static DEFINE_PER_CPU(struct loongson3_freq_data
*, freq_data
);
176 static inline int do_service_request(u32 id
, u32 info
, u32 cmd
, u32 val
, u32 extra
)
179 unsigned int cpu
= raw_smp_processor_id();
180 unsigned int package
= cpu_data
[cpu
].package
;
181 union smc_message msg
, last
;
183 mutex_lock(&cpufreq_mutex
[package
]);
185 last
.value
= iocsr_read32(LOONGARCH_IOCSR_SMCMBX
);
186 if (!last
.complete
) {
187 mutex_unlock(&cpufreq_mutex
[package
]);
198 iocsr_write32(msg
.value
, LOONGARCH_IOCSR_SMCMBX
);
199 iocsr_write32(iocsr_read32(LOONGARCH_IOCSR_MISC_FUNC
) | IOCSR_MISC_FUNC_SOFT_INT
,
200 LOONGARCH_IOCSR_MISC_FUNC
);
202 for (retries
= 0; retries
< 10000; retries
++) {
203 msg
.value
= iocsr_read32(LOONGARCH_IOCSR_SMCMBX
);
210 if (!msg
.complete
|| msg
.cmd
!= CMD_OK
) {
211 mutex_unlock(&cpufreq_mutex
[package
]);
215 mutex_unlock(&cpufreq_mutex
[package
]);
220 static unsigned int loongson3_cpufreq_get(unsigned int cpu
)
224 ret
= do_service_request(cpu
, FREQ_INFO_TYPE_FREQ
, CMD_GET_FREQ_INFO
, 0, 0);
229 static int loongson3_cpufreq_target(struct cpufreq_policy
*policy
, unsigned int index
)
233 ret
= do_service_request(cpu_data
[policy
->cpu
].core
,
234 FREQ_INFO_TYPE_LEVEL
, CMD_SET_FREQ_INFO
, index
, 0);
236 return (ret
>= 0) ? 0 : ret
;
239 static int configure_freq_table(int cpu
)
241 int i
, ret
, boost_level
, max_level
, freq_level
;
242 struct platform_device
*pdev
= cpufreq_get_driver_data();
243 struct loongson3_freq_data
*data
;
245 if (per_cpu(freq_data
, cpu
))
248 ret
= do_service_request(cpu
, 0, CMD_GET_FREQ_LEVEL_NUM
, 0, 0);
253 ret
= do_service_request(cpu
, 0, CMD_GET_FREQ_BOOST_LEVEL
, 0, 0);
258 freq_level
= min(max_level
, FREQ_MAX_LEVEL
);
259 data
= devm_kzalloc(&pdev
->dev
, struct_size(data
, table
, freq_level
+ 1), GFP_KERNEL
);
263 data
->def_freq_level
= boost_level
- 1;
265 for (i
= 0; i
< freq_level
; i
++) {
266 ret
= do_service_request(cpu
, FREQ_INFO_TYPE_FREQ
, CMD_GET_FREQ_LEVEL_INFO
, i
, 0);
268 devm_kfree(&pdev
->dev
, data
);
272 data
->table
[i
].frequency
= ret
* KILO
;
273 data
->table
[i
].flags
= (i
>= boost_level
) ? CPUFREQ_BOOST_FREQ
: 0;
276 data
->table
[freq_level
].flags
= 0;
277 data
->table
[freq_level
].frequency
= CPUFREQ_TABLE_END
;
279 per_cpu(freq_data
, cpu
) = data
;
284 static int loongson3_cpufreq_cpu_init(struct cpufreq_policy
*policy
)
286 int i
, ret
, cpu
= policy
->cpu
;
288 ret
= configure_freq_table(cpu
);
292 policy
->cpuinfo
.transition_latency
= 10000;
293 policy
->freq_table
= per_cpu(freq_data
, cpu
)->table
;
294 policy
->suspend_freq
= policy
->freq_table
[per_cpu(freq_data
, cpu
)->def_freq_level
].frequency
;
295 cpumask_copy(policy
->cpus
, topology_sibling_cpumask(cpu
));
297 for_each_cpu(i
, policy
->cpus
) {
299 per_cpu(freq_data
, i
) = per_cpu(freq_data
, cpu
);
302 if (policy_has_boost_freq(policy
)) {
303 ret
= cpufreq_enable_boost_support();
305 pr_warn("cpufreq: Failed to enable boost: %d\n", ret
);
308 loongson3_cpufreq_driver
.boost_enabled
= true;
314 static void loongson3_cpufreq_cpu_exit(struct cpufreq_policy
*policy
)
316 int cpu
= policy
->cpu
;
318 loongson3_cpufreq_target(policy
, per_cpu(freq_data
, cpu
)->def_freq_level
);
321 static int loongson3_cpufreq_cpu_online(struct cpufreq_policy
*policy
)
326 static int loongson3_cpufreq_cpu_offline(struct cpufreq_policy
*policy
)
331 static struct cpufreq_driver loongson3_cpufreq_driver
= {
333 .flags
= CPUFREQ_CONST_LOOPS
,
334 .init
= loongson3_cpufreq_cpu_init
,
335 .exit
= loongson3_cpufreq_cpu_exit
,
336 .online
= loongson3_cpufreq_cpu_online
,
337 .offline
= loongson3_cpufreq_cpu_offline
,
338 .get
= loongson3_cpufreq_get
,
339 .target_index
= loongson3_cpufreq_target
,
340 .attr
= cpufreq_generic_attr
,
341 .verify
= cpufreq_generic_frequency_table_verify
,
342 .suspend
= cpufreq_generic_suspend
,
345 static int loongson3_cpufreq_probe(struct platform_device
*pdev
)
349 for (i
= 0; i
< MAX_PACKAGES
; i
++)
350 devm_mutex_init(&pdev
->dev
, &cpufreq_mutex
[i
]);
352 ret
= do_service_request(0, 0, CMD_GET_VERSION
, 0, 0);
356 ret
= do_service_request(FEATURE_DVFS
, 0, CMD_SET_FEATURE
,
357 FEATURE_DVFS_ENABLE
| FEATURE_DVFS_BOOST
, 0);
361 loongson3_cpufreq_driver
.driver_data
= pdev
;
363 ret
= cpufreq_register_driver(&loongson3_cpufreq_driver
);
367 pr_info("cpufreq: Loongson-3 CPU frequency driver.\n");
372 static void loongson3_cpufreq_remove(struct platform_device
*pdev
)
374 cpufreq_unregister_driver(&loongson3_cpufreq_driver
);
377 static struct platform_device_id cpufreq_id_table
[] = {
378 { "loongson3_cpufreq", },
381 MODULE_DEVICE_TABLE(platform
, cpufreq_id_table
);
383 static struct platform_driver loongson3_platform_driver
= {
385 .name
= "loongson3_cpufreq",
387 .id_table
= cpufreq_id_table
,
388 .probe
= loongson3_cpufreq_probe
,
389 .remove_new
= loongson3_cpufreq_remove
,
391 module_platform_driver(loongson3_platform_driver
);
393 MODULE_AUTHOR("Huacai Chen <chenhuacai@loongson.cn>");
394 MODULE_DESCRIPTION("CPUFreq driver for Loongson-3 processors");
395 MODULE_LICENSE("GPL");