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