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"); 396