Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

cpufreq: Add Loongson-3 CPUFreq driver support

Some of LoongArch processors (Loongson-3 series) support DVFS, their
IOCSR.FEATURES has IOCSRF_FREQSCALE set. And they has a micro-core in
the package called SMC (System Management Controller), which can be
used to detect temperature, control fans, scale frequency and voltage,
etc.

The Loongson-3 CPUFreq driver is very simple now, it communicate with
SMC, get DVFS info, set target frequency from CPUFreq core, and so on.

There is a command list to interact with SMC, widely-used commands in
the CPUFreq driver include:

CMD_GET_VERSION: Get SMC firmware version.

CMD_GET_FEATURE: Get enabled SMC features.

CMD_SET_FEATURE: Enable SMC features, such as basic DVFS, BOOST.

CMD_GET_FREQ_LEVEL_NUM: Get the number of all frequency levels.

CMD_GET_FREQ_BOOST_LEVEL: Get the first boost frequency level.

CMD_GET_FREQ_LEVEL_INFO: Get the detail info of a frequency level.

CMD_GET_FREQ_INFO: Get the current frequency.

CMD_SET_FREQ_INFO: Set the target frequency.

In future we will add automatic frequency scaling, which is similar to
Intel's HWP (HardWare P-State).

Signed-off-by: Binbin Zhou <zhoubinbin@loongson.cn>
Signed-off-by: Huacai Chen <chenhuacai@loongson.cn>
[ Viresh: Minor formatting cleanups, change return type of exit() to
void and use devm_mutex_init() ]
Signed-off-by: Viresh Kumar <viresh.kumar@linaro.org>

authored by

Huacai Chen and committed by
Viresh Kumar
ccf51454 b4b1ddc9

+409
+1
MAINTAINERS
··· 12969 12969 F: Documentation/translations/zh_CN/arch/loongarch/ 12970 12970 F: arch/loongarch/ 12971 12971 F: drivers/*/*loongarch* 12972 + F: drivers/cpufreq/loongson3_cpufreq.c 12972 12973 12973 12974 LOONGSON GPIO DRIVER 12974 12975 M: Yinbo Zhu <zhuyinbo@loongson.cn>
+12
drivers/cpufreq/Kconfig
··· 262 262 If in doubt, say N. 263 263 endif 264 264 265 + if LOONGARCH 266 + config LOONGSON3_CPUFREQ 267 + tristate "Loongson3 CPUFreq Driver" 268 + help 269 + This option adds a CPUFreq driver for Loongson processors which 270 + support software configurable cpu frequency. 271 + 272 + Loongson-3 family processors support this feature. 273 + 274 + If in doubt, say N. 275 + endif 276 + 265 277 if SPARC64 266 278 config SPARC_US3_CPUFREQ 267 279 tristate "UltraSPARC-III CPU Frequency driver"
+1
drivers/cpufreq/Makefile
··· 103 103 # Other platform drivers 104 104 obj-$(CONFIG_BMIPS_CPUFREQ) += bmips-cpufreq.o 105 105 obj-$(CONFIG_LOONGSON2_CPUFREQ) += loongson2_cpufreq.o 106 + obj-$(CONFIG_LOONGSON3_CPUFREQ) += loongson3_cpufreq.o 106 107 obj-$(CONFIG_SH_CPU_FREQ) += sh-cpufreq.o 107 108 obj-$(CONFIG_SPARC_US2E_CPUFREQ) += sparc-us2e-cpufreq.o 108 109 obj-$(CONFIG_SPARC_US3_CPUFREQ) += sparc-us3-cpufreq.o
+395
drivers/cpufreq/loongson3_cpufreq.c
··· 1 + // SPDX-License-Identifier: GPL-2.0-only 2 + /* 3 + * CPUFreq driver for the Loongson-3 processors. 4 + * 5 + * All revisions of Loongson-3 processor support cpu_has_scalefreq feature. 6 + * 7 + * Author: Huacai Chen <chenhuacai@loongson.cn> 8 + * Copyright (C) 2024 Loongson Technology Corporation Limited 9 + */ 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> 15 + 16 + #include <asm/idle.h> 17 + #include <asm/loongarch.h> 18 + #include <asm/loongson.h> 19 + 20 + /* Message */ 21 + union smc_message { 22 + u32 value; 23 + struct { 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 BIT(0) 69 + #define FEATURE_SENSOR_SAMPLE BIT(1) 70 + 71 + /* Fan feature flags */ 72 + #define FEATURE_FAN_ENABLE BIT(0) 73 + #define FEATURE_FAN_AUTO BIT(1) 74 + 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) 80 + 81 + /* Sensor commands */ 82 + /* 83 + * CMD_GET_SENSOR_NUM - Get number of sensors 84 + * Input: none 85 + * Output: number 86 + */ 87 + #define CMD_GET_SENSOR_NUM 0x4 88 + 89 + /* 90 + * CMD_GET_SENSOR_STATUS - Get sensor status 91 + * Input: sensor ID, type 92 + * Output: sensor status 93 + */ 94 + #define CMD_GET_SENSOR_STATUS 0x5 95 + 96 + /* Sensor types */ 97 + #define SENSOR_INFO_TYPE 0 98 + #define SENSOR_INFO_TYPE_TEMP 1 99 + 100 + /* Fan commands */ 101 + /* 102 + * CMD_GET_FAN_NUM - Get number of fans 103 + * Input: none 104 + * Output: number 105 + */ 106 + #define CMD_GET_FAN_NUM 0x6 107 + 108 + /* 109 + * CMD_GET_FAN_INFO - Get fan status 110 + * Input: fan ID, type 111 + * Output: fan info 112 + */ 113 + #define CMD_GET_FAN_INFO 0x7 114 + 115 + /* 116 + * CMD_SET_FAN_INFO - Set fan status 117 + * Input: fan ID, type, value 118 + * Output: none 119 + */ 120 + #define CMD_SET_FAN_INFO 0x8 121 + 122 + /* Fan types */ 123 + #define FAN_INFO_TYPE_LEVEL 0 124 + 125 + /* DVFS commands */ 126 + /* 127 + * CMD_GET_FREQ_LEVEL_NUM - Get number of freq levels 128 + * Input: CPU ID 129 + * Output: number 130 + */ 131 + #define CMD_GET_FREQ_LEVEL_NUM 0x9 132 + 133 + /* 134 + * CMD_GET_FREQ_BOOST_LEVEL - Get the first boost level 135 + * Input: CPU ID 136 + * Output: number 137 + */ 138 + #define CMD_GET_FREQ_BOOST_LEVEL 0x10 139 + 140 + /* 141 + * CMD_GET_FREQ_LEVEL_INFO - Get freq level info 142 + * Input: CPU ID, level ID 143 + * Output: level info 144 + */ 145 + #define CMD_GET_FREQ_LEVEL_INFO 0x11 146 + 147 + /* 148 + * CMD_GET_FREQ_INFO - Get freq info 149 + * Input: CPU ID, type 150 + * Output: freq info 151 + */ 152 + #define CMD_GET_FREQ_INFO 0x12 153 + 154 + /* 155 + * CMD_SET_FREQ_INFO - Set freq info 156 + * Input: CPU ID, type, value 157 + * Output: none 158 + */ 159 + #define CMD_SET_FREQ_INFO 0x13 160 + 161 + /* Freq types */ 162 + #define FREQ_INFO_TYPE_FREQ 0 163 + #define FREQ_INFO_TYPE_LEVEL 1 164 + 165 + #define FREQ_MAX_LEVEL 16 166 + 167 + struct loongson3_freq_data { 168 + unsigned int def_freq_level; 169 + struct cpufreq_frequency_table table[]; 170 + }; 171 + 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); 175 + 176 + static inline int do_service_request(u32 id, u32 info, u32 cmd, u32 val, u32 extra) 177 + { 178 + int retries; 179 + unsigned int cpu = smp_processor_id(); 180 + unsigned int package = cpu_data[cpu].package; 181 + union smc_message msg, last; 182 + 183 + mutex_lock(&cpufreq_mutex[package]); 184 + 185 + last.value = iocsr_read32(LOONGARCH_IOCSR_SMCMBX); 186 + if (!last.complete) { 187 + mutex_unlock(&cpufreq_mutex[package]); 188 + return -EPERM; 189 + } 190 + 191 + msg.id = id; 192 + msg.info = info; 193 + msg.cmd = cmd; 194 + msg.val = val; 195 + msg.extra = extra; 196 + msg.complete = 0; 197 + 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); 201 + 202 + for (retries = 0; retries < 10000; retries++) { 203 + msg.value = iocsr_read32(LOONGARCH_IOCSR_SMCMBX); 204 + if (msg.complete) 205 + break; 206 + 207 + usleep_range(8, 12); 208 + } 209 + 210 + if (!msg.complete || msg.cmd != CMD_OK) { 211 + mutex_unlock(&cpufreq_mutex[package]); 212 + return -EPERM; 213 + } 214 + 215 + mutex_unlock(&cpufreq_mutex[package]); 216 + 217 + return msg.val; 218 + } 219 + 220 + static unsigned int loongson3_cpufreq_get(unsigned int cpu) 221 + { 222 + int ret; 223 + 224 + ret = do_service_request(cpu, FREQ_INFO_TYPE_FREQ, CMD_GET_FREQ_INFO, 0, 0); 225 + 226 + return ret * KILO; 227 + } 228 + 229 + static int loongson3_cpufreq_target(struct cpufreq_policy *policy, unsigned int index) 230 + { 231 + int ret; 232 + 233 + ret = do_service_request(cpu_data[policy->cpu].core, 234 + FREQ_INFO_TYPE_LEVEL, CMD_SET_FREQ_INFO, index, 0); 235 + 236 + return (ret >= 0) ? 0 : ret; 237 + } 238 + 239 + static int configure_freq_table(int cpu) 240 + { 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; 244 + 245 + if (per_cpu(freq_data, cpu)) 246 + return 0; 247 + 248 + ret = do_service_request(cpu, 0, CMD_GET_FREQ_LEVEL_NUM, 0, 0); 249 + if (ret < 0) 250 + return ret; 251 + max_level = ret; 252 + 253 + ret = do_service_request(cpu, 0, CMD_GET_FREQ_BOOST_LEVEL, 0, 0); 254 + if (ret < 0) 255 + return ret; 256 + boost_level = ret; 257 + 258 + freq_level = min(max_level, FREQ_MAX_LEVEL); 259 + data = devm_kzalloc(&pdev->dev, struct_size(data, table, freq_level + 1), GFP_KERNEL); 260 + if (!data) 261 + return -ENOMEM; 262 + 263 + data->def_freq_level = boost_level - 1; 264 + 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); 267 + if (ret < 0) { 268 + devm_kfree(&pdev->dev, data); 269 + return ret; 270 + } 271 + 272 + data->table[i].frequency = ret * KILO; 273 + data->table[i].flags = (i >= boost_level) ? CPUFREQ_BOOST_FREQ : 0; 274 + } 275 + 276 + data->table[freq_level].flags = 0; 277 + data->table[freq_level].frequency = CPUFREQ_TABLE_END; 278 + 279 + per_cpu(freq_data, cpu) = data; 280 + 281 + return 0; 282 + } 283 + 284 + static int loongson3_cpufreq_cpu_init(struct cpufreq_policy *policy) 285 + { 286 + int i, ret, cpu = policy->cpu; 287 + 288 + ret = configure_freq_table(cpu); 289 + if (ret < 0) 290 + return ret; 291 + 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)); 296 + 297 + for_each_cpu(i, policy->cpus) { 298 + if (i != cpu) 299 + per_cpu(freq_data, i) = per_cpu(freq_data, cpu); 300 + } 301 + 302 + if (policy_has_boost_freq(policy)) { 303 + ret = cpufreq_enable_boost_support(); 304 + if (ret < 0) { 305 + pr_warn("cpufreq: Failed to enable boost: %d\n", ret); 306 + return ret; 307 + } 308 + loongson3_cpufreq_driver.boost_enabled = true; 309 + } 310 + 311 + return 0; 312 + } 313 + 314 + static void loongson3_cpufreq_cpu_exit(struct cpufreq_policy *policy) 315 + { 316 + int cpu = policy->cpu; 317 + 318 + loongson3_cpufreq_target(policy, per_cpu(freq_data, cpu)->def_freq_level); 319 + } 320 + 321 + static int loongson3_cpufreq_cpu_online(struct cpufreq_policy *policy) 322 + { 323 + return 0; 324 + } 325 + 326 + static int loongson3_cpufreq_cpu_offline(struct cpufreq_policy *policy) 327 + { 328 + return 0; 329 + } 330 + 331 + static struct cpufreq_driver loongson3_cpufreq_driver = { 332 + .name = "loongson3", 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, 343 + }; 344 + 345 + static int loongson3_cpufreq_probe(struct platform_device *pdev) 346 + { 347 + int i, ret; 348 + 349 + for (i = 0; i < MAX_PACKAGES; i++) 350 + devm_mutex_init(&pdev->dev, &cpufreq_mutex[i]); 351 + 352 + ret = do_service_request(0, 0, CMD_GET_VERSION, 0, 0); 353 + if (ret <= 0) 354 + return -EPERM; 355 + 356 + ret = do_service_request(FEATURE_DVFS, 0, CMD_SET_FEATURE, 357 + FEATURE_DVFS_ENABLE | FEATURE_DVFS_BOOST, 0); 358 + if (ret < 0) 359 + return -EPERM; 360 + 361 + loongson3_cpufreq_driver.driver_data = pdev; 362 + 363 + ret = cpufreq_register_driver(&loongson3_cpufreq_driver); 364 + if (ret) 365 + return ret; 366 + 367 + pr_info("cpufreq: Loongson-3 CPU frequency driver.\n"); 368 + 369 + return 0; 370 + } 371 + 372 + static void loongson3_cpufreq_remove(struct platform_device *pdev) 373 + { 374 + cpufreq_unregister_driver(&loongson3_cpufreq_driver); 375 + } 376 + 377 + static struct platform_device_id cpufreq_id_table[] = { 378 + { "loongson3_cpufreq", }, 379 + { /* sentinel */ } 380 + }; 381 + MODULE_DEVICE_TABLE(platform, cpufreq_id_table); 382 + 383 + static struct platform_driver loongson3_platform_driver = { 384 + .driver = { 385 + .name = "loongson3_cpufreq", 386 + }, 387 + .id_table = cpufreq_id_table, 388 + .probe = loongson3_cpufreq_probe, 389 + .remove_new = loongson3_cpufreq_remove, 390 + }; 391 + module_platform_driver(loongson3_platform_driver); 392 + 393 + MODULE_AUTHOR("Huacai Chen <chenhuacai@loongson.cn>"); 394 + MODULE_DESCRIPTION("CPUFreq driver for Loongson-3 processors"); 395 + MODULE_LICENSE("GPL");