loongson3_cpufreq.c 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389
  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. #include <asm/idle.h>
  16. #include <asm/loongarch.h>
  17. #include <asm/loongson.h>
  18. /* Message */
  19. union smc_message {
  20. u32 value;
  21. struct {
  22. u32 id : 4;
  23. u32 info : 4;
  24. u32 val : 16;
  25. u32 cmd : 6;
  26. u32 extra : 1;
  27. u32 complete : 1;
  28. };
  29. };
  30. /* Command return values */
  31. #define CMD_OK 0 /* No error */
  32. #define CMD_ERROR 1 /* Regular error */
  33. #define CMD_NOCMD 2 /* Command does not support */
  34. #define CMD_INVAL 3 /* Invalid Parameter */
  35. /* Version commands */
  36. /*
  37. * CMD_GET_VERSION - Get interface version
  38. * Input: none
  39. * Output: version
  40. */
  41. #define CMD_GET_VERSION 0x1
  42. /* Feature commands */
  43. /*
  44. * CMD_GET_FEATURE - Get feature state
  45. * Input: feature ID
  46. * Output: feature flag
  47. */
  48. #define CMD_GET_FEATURE 0x2
  49. /*
  50. * CMD_SET_FEATURE - Set feature state
  51. * Input: feature ID, feature flag
  52. * output: none
  53. */
  54. #define CMD_SET_FEATURE 0x3
  55. /* Feature IDs */
  56. #define FEATURE_SENSOR 0
  57. #define FEATURE_FAN 1
  58. #define FEATURE_DVFS 2
  59. /* Sensor feature flags */
  60. #define FEATURE_SENSOR_ENABLE BIT(0)
  61. #define FEATURE_SENSOR_SAMPLE BIT(1)
  62. /* Fan feature flags */
  63. #define FEATURE_FAN_ENABLE BIT(0)
  64. #define FEATURE_FAN_AUTO BIT(1)
  65. /* DVFS feature flags */
  66. #define FEATURE_DVFS_ENABLE BIT(0)
  67. #define FEATURE_DVFS_BOOST BIT(1)
  68. #define FEATURE_DVFS_AUTO BIT(2)
  69. #define FEATURE_DVFS_SINGLE_BOOST BIT(3)
  70. /* Sensor commands */
  71. /*
  72. * CMD_GET_SENSOR_NUM - Get number of sensors
  73. * Input: none
  74. * Output: number
  75. */
  76. #define CMD_GET_SENSOR_NUM 0x4
  77. /*
  78. * CMD_GET_SENSOR_STATUS - Get sensor status
  79. * Input: sensor ID, type
  80. * Output: sensor status
  81. */
  82. #define CMD_GET_SENSOR_STATUS 0x5
  83. /* Sensor types */
  84. #define SENSOR_INFO_TYPE 0
  85. #define SENSOR_INFO_TYPE_TEMP 1
  86. /* Fan commands */
  87. /*
  88. * CMD_GET_FAN_NUM - Get number of fans
  89. * Input: none
  90. * Output: number
  91. */
  92. #define CMD_GET_FAN_NUM 0x6
  93. /*
  94. * CMD_GET_FAN_INFO - Get fan status
  95. * Input: fan ID, type
  96. * Output: fan info
  97. */
  98. #define CMD_GET_FAN_INFO 0x7
  99. /*
  100. * CMD_SET_FAN_INFO - Set fan status
  101. * Input: fan ID, type, value
  102. * Output: none
  103. */
  104. #define CMD_SET_FAN_INFO 0x8
  105. /* Fan types */
  106. #define FAN_INFO_TYPE_LEVEL 0
  107. /* DVFS commands */
  108. /*
  109. * CMD_GET_FREQ_LEVEL_NUM - Get number of freq levels
  110. * Input: CPU ID
  111. * Output: number
  112. */
  113. #define CMD_GET_FREQ_LEVEL_NUM 0x9
  114. /*
  115. * CMD_GET_FREQ_BOOST_LEVEL - Get the first boost level
  116. * Input: CPU ID
  117. * Output: number
  118. */
  119. #define CMD_GET_FREQ_BOOST_LEVEL 0x10
  120. /*
  121. * CMD_GET_FREQ_LEVEL_INFO - Get freq level info
  122. * Input: CPU ID, level ID
  123. * Output: level info
  124. */
  125. #define CMD_GET_FREQ_LEVEL_INFO 0x11
  126. /*
  127. * CMD_GET_FREQ_INFO - Get freq info
  128. * Input: CPU ID, type
  129. * Output: freq info
  130. */
  131. #define CMD_GET_FREQ_INFO 0x12
  132. /*
  133. * CMD_SET_FREQ_INFO - Set freq info
  134. * Input: CPU ID, type, value
  135. * Output: none
  136. */
  137. #define CMD_SET_FREQ_INFO 0x13
  138. /* Freq types */
  139. #define FREQ_INFO_TYPE_FREQ 0
  140. #define FREQ_INFO_TYPE_LEVEL 1
  141. #define FREQ_MAX_LEVEL 16
  142. struct loongson3_freq_data {
  143. unsigned int def_freq_level;
  144. struct cpufreq_frequency_table table[];
  145. };
  146. static struct mutex cpufreq_mutex[MAX_PACKAGES];
  147. static struct cpufreq_driver loongson3_cpufreq_driver;
  148. static DEFINE_PER_CPU(struct loongson3_freq_data *, freq_data);
  149. static inline int do_service_request(u32 id, u32 info, u32 cmd, u32 val, u32 extra)
  150. {
  151. int retries;
  152. unsigned int cpu = raw_smp_processor_id();
  153. unsigned int package = cpu_data[cpu].package;
  154. union smc_message msg, last;
  155. mutex_lock(&cpufreq_mutex[package]);
  156. last.value = iocsr_read32(LOONGARCH_IOCSR_SMCMBX);
  157. if (!last.complete) {
  158. mutex_unlock(&cpufreq_mutex[package]);
  159. return -EPERM;
  160. }
  161. msg.id = id;
  162. msg.info = info;
  163. msg.cmd = cmd;
  164. msg.val = val;
  165. msg.extra = extra;
  166. msg.complete = 0;
  167. iocsr_write32(msg.value, LOONGARCH_IOCSR_SMCMBX);
  168. iocsr_write32(iocsr_read32(LOONGARCH_IOCSR_MISC_FUNC) | IOCSR_MISC_FUNC_SOFT_INT,
  169. LOONGARCH_IOCSR_MISC_FUNC);
  170. for (retries = 0; retries < 10000; retries++) {
  171. msg.value = iocsr_read32(LOONGARCH_IOCSR_SMCMBX);
  172. if (msg.complete)
  173. break;
  174. usleep_range(8, 12);
  175. }
  176. if (!msg.complete || msg.cmd != CMD_OK) {
  177. mutex_unlock(&cpufreq_mutex[package]);
  178. return -EPERM;
  179. }
  180. mutex_unlock(&cpufreq_mutex[package]);
  181. return msg.val;
  182. }
  183. static unsigned int loongson3_cpufreq_get(unsigned int cpu)
  184. {
  185. int ret;
  186. ret = do_service_request(cpu, FREQ_INFO_TYPE_FREQ, CMD_GET_FREQ_INFO, 0, 0);
  187. return ret * KILO;
  188. }
  189. static int loongson3_cpufreq_target(struct cpufreq_policy *policy, unsigned int index)
  190. {
  191. int ret;
  192. ret = do_service_request(cpu_data[policy->cpu].core,
  193. FREQ_INFO_TYPE_LEVEL, CMD_SET_FREQ_INFO, index, 0);
  194. return (ret >= 0) ? 0 : ret;
  195. }
  196. static int configure_freq_table(int cpu)
  197. {
  198. int i, ret, boost_level, max_level, freq_level;
  199. struct platform_device *pdev = cpufreq_get_driver_data();
  200. struct loongson3_freq_data *data;
  201. if (per_cpu(freq_data, cpu))
  202. return 0;
  203. ret = do_service_request(cpu, 0, CMD_GET_FREQ_LEVEL_NUM, 0, 0);
  204. if (ret < 0)
  205. return ret;
  206. max_level = ret;
  207. ret = do_service_request(cpu, 0, CMD_GET_FREQ_BOOST_LEVEL, 0, 0);
  208. if (ret < 0)
  209. return ret;
  210. boost_level = ret;
  211. freq_level = min(max_level, FREQ_MAX_LEVEL);
  212. data = devm_kzalloc(&pdev->dev, struct_size(data, table, freq_level + 1), GFP_KERNEL);
  213. if (!data)
  214. return -ENOMEM;
  215. data->def_freq_level = boost_level - 1;
  216. for (i = 0; i < freq_level; i++) {
  217. ret = do_service_request(cpu, FREQ_INFO_TYPE_FREQ, CMD_GET_FREQ_LEVEL_INFO, i, 0);
  218. if (ret < 0) {
  219. devm_kfree(&pdev->dev, data);
  220. return ret;
  221. }
  222. data->table[i].frequency = ret * KILO;
  223. data->table[i].flags = (i >= boost_level) ? CPUFREQ_BOOST_FREQ : 0;
  224. }
  225. data->table[freq_level].flags = 0;
  226. data->table[freq_level].frequency = CPUFREQ_TABLE_END;
  227. per_cpu(freq_data, cpu) = data;
  228. return 0;
  229. }
  230. static int loongson3_cpufreq_cpu_init(struct cpufreq_policy *policy)
  231. {
  232. int i, ret, cpu = policy->cpu;
  233. ret = configure_freq_table(cpu);
  234. if (ret < 0)
  235. return ret;
  236. policy->cpuinfo.transition_latency = 10000;
  237. policy->freq_table = per_cpu(freq_data, cpu)->table;
  238. policy->suspend_freq = policy->freq_table[per_cpu(freq_data, cpu)->def_freq_level].frequency;
  239. cpumask_copy(policy->cpus, topology_sibling_cpumask(cpu));
  240. for_each_cpu(i, policy->cpus) {
  241. if (i != cpu)
  242. per_cpu(freq_data, i) = per_cpu(freq_data, cpu);
  243. }
  244. return 0;
  245. }
  246. static void loongson3_cpufreq_cpu_exit(struct cpufreq_policy *policy)
  247. {
  248. int cpu = policy->cpu;
  249. loongson3_cpufreq_target(policy, per_cpu(freq_data, cpu)->def_freq_level);
  250. }
  251. static int loongson3_cpufreq_cpu_online(struct cpufreq_policy *policy)
  252. {
  253. return 0;
  254. }
  255. static int loongson3_cpufreq_cpu_offline(struct cpufreq_policy *policy)
  256. {
  257. return 0;
  258. }
  259. static struct cpufreq_driver loongson3_cpufreq_driver = {
  260. .name = "loongson3",
  261. .flags = CPUFREQ_CONST_LOOPS,
  262. .init = loongson3_cpufreq_cpu_init,
  263. .exit = loongson3_cpufreq_cpu_exit,
  264. .online = loongson3_cpufreq_cpu_online,
  265. .offline = loongson3_cpufreq_cpu_offline,
  266. .get = loongson3_cpufreq_get,
  267. .target_index = loongson3_cpufreq_target,
  268. .verify = cpufreq_generic_frequency_table_verify,
  269. .set_boost = cpufreq_boost_set_sw,
  270. .suspend = cpufreq_generic_suspend,
  271. };
  272. static int loongson3_cpufreq_probe(struct platform_device *pdev)
  273. {
  274. int i, ret;
  275. for (i = 0; i < MAX_PACKAGES; i++) {
  276. ret = devm_mutex_init(&pdev->dev, &cpufreq_mutex[i]);
  277. if (ret)
  278. return ret;
  279. }
  280. ret = do_service_request(0, 0, CMD_GET_VERSION, 0, 0);
  281. if (ret <= 0)
  282. return -EPERM;
  283. ret = do_service_request(FEATURE_DVFS, 0, CMD_SET_FEATURE,
  284. FEATURE_DVFS_ENABLE | FEATURE_DVFS_BOOST, 0);
  285. if (ret < 0)
  286. return -EPERM;
  287. loongson3_cpufreq_driver.driver_data = pdev;
  288. ret = cpufreq_register_driver(&loongson3_cpufreq_driver);
  289. if (ret)
  290. return ret;
  291. pr_info("cpufreq: Loongson-3 CPU frequency driver.\n");
  292. return 0;
  293. }
  294. static void loongson3_cpufreq_remove(struct platform_device *pdev)
  295. {
  296. cpufreq_unregister_driver(&loongson3_cpufreq_driver);
  297. }
  298. static struct platform_device_id cpufreq_id_table[] = {
  299. { "loongson3_cpufreq", },
  300. { /* sentinel */ }
  301. };
  302. MODULE_DEVICE_TABLE(platform, cpufreq_id_table);
  303. static struct platform_driver loongson3_platform_driver = {
  304. .driver = {
  305. .name = "loongson3_cpufreq",
  306. },
  307. .id_table = cpufreq_id_table,
  308. .probe = loongson3_cpufreq_probe,
  309. .remove = loongson3_cpufreq_remove,
  310. };
  311. module_platform_driver(loongson3_platform_driver);
  312. MODULE_AUTHOR("Huacai Chen <chenhuacai@loongson.cn>");
  313. MODULE_DESCRIPTION("CPUFreq driver for Loongson-3 processors");
  314. MODULE_LICENSE("GPL");