396 lines · c
1// SPDX-License-Identifier: GPL-2.0-only2/*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 Limited9 */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 */21union 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 version42 * Input: none43 * Output: version44 */45#define CMD_GET_VERSION 0x146 47/* Feature commands */48/*49 * CMD_GET_FEATURE - Get feature state50 * Input: feature ID51 * Output: feature flag52 */53#define CMD_GET_FEATURE 0x254 55/*56 * CMD_SET_FEATURE - Set feature state57 * Input: feature ID, feature flag58 * output: none59 */60#define CMD_SET_FEATURE 0x361 62/* Feature IDs */63#define FEATURE_SENSOR 064#define FEATURE_FAN 165#define FEATURE_DVFS 266 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 sensors84 * Input: none85 * Output: number86 */87#define CMD_GET_SENSOR_NUM 0x488 89/*90 * CMD_GET_SENSOR_STATUS - Get sensor status91 * Input: sensor ID, type92 * Output: sensor status93 */94#define CMD_GET_SENSOR_STATUS 0x595 96/* Sensor types */97#define SENSOR_INFO_TYPE 098#define SENSOR_INFO_TYPE_TEMP 199 100/* Fan commands */101/*102 * CMD_GET_FAN_NUM - Get number of fans103 * Input: none104 * Output: number105 */106#define CMD_GET_FAN_NUM 0x6107 108/*109 * CMD_GET_FAN_INFO - Get fan status110 * Input: fan ID, type111 * Output: fan info112 */113#define CMD_GET_FAN_INFO 0x7114 115/*116 * CMD_SET_FAN_INFO - Set fan status117 * Input: fan ID, type, value118 * Output: none119 */120#define CMD_SET_FAN_INFO 0x8121 122/* Fan types */123#define FAN_INFO_TYPE_LEVEL 0124 125/* DVFS commands */126/*127 * CMD_GET_FREQ_LEVEL_NUM - Get number of freq levels128 * Input: CPU ID129 * Output: number130 */131#define CMD_GET_FREQ_LEVEL_NUM 0x9132 133/*134 * CMD_GET_FREQ_BOOST_LEVEL - Get the first boost level135 * Input: CPU ID136 * Output: number137 */138#define CMD_GET_FREQ_BOOST_LEVEL 0x10139 140/*141 * CMD_GET_FREQ_LEVEL_INFO - Get freq level info142 * Input: CPU ID, level ID143 * Output: level info144 */145#define CMD_GET_FREQ_LEVEL_INFO 0x11146 147/*148 * CMD_GET_FREQ_INFO - Get freq info149 * Input: CPU ID, type150 * Output: freq info151 */152#define CMD_GET_FREQ_INFO 0x12153 154/*155 * CMD_SET_FREQ_INFO - Set freq info156 * Input: CPU ID, type, value157 * Output: none158 */159#define CMD_SET_FREQ_INFO 0x13160 161/* Freq types */162#define FREQ_INFO_TYPE_FREQ 0163#define FREQ_INFO_TYPE_LEVEL 1164 165#define FREQ_MAX_LEVEL 16166 167struct loongson3_freq_data {168 unsigned int def_freq_level;169 struct cpufreq_frequency_table table[];170};171 172static struct mutex cpufreq_mutex[MAX_PACKAGES];173static struct cpufreq_driver loongson3_cpufreq_driver;174static DEFINE_PER_CPU(struct loongson3_freq_data *, freq_data);175 176static inline int do_service_request(u32 id, u32 info, u32 cmd, u32 val, u32 extra)177{178 int retries;179 unsigned int cpu = raw_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 220static 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 229static 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 239static 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 284static 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 314static 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 321static int loongson3_cpufreq_cpu_online(struct cpufreq_policy *policy)322{323 return 0;324}325 326static int loongson3_cpufreq_cpu_offline(struct cpufreq_policy *policy)327{328 return 0;329}330 331static 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 345static 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 372static void loongson3_cpufreq_remove(struct platform_device *pdev)373{374 cpufreq_unregister_driver(&loongson3_cpufreq_driver);375}376 377static struct platform_device_id cpufreq_id_table[] = {378 { "loongson3_cpufreq", },379 { /* sentinel */ }380};381MODULE_DEVICE_TABLE(platform, cpufreq_id_table);382 383static 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};391module_platform_driver(loongson3_platform_driver);392 393MODULE_AUTHOR("Huacai Chen <chenhuacai@loongson.cn>");394MODULE_DESCRIPTION("CPUFreq driver for Loongson-3 processors");395MODULE_LICENSE("GPL");396