sun50i-cpufreq-nvmem.c 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218
  1. // SPDX-License-Identifier: GPL-2.0
  2. /*
  3. * Allwinner CPUFreq nvmem based driver
  4. *
  5. * The sun50i-cpufreq-nvmem driver reads the efuse value from the SoC to
  6. * provide the OPP framework with required information.
  7. *
  8. * Copyright (C) 2019 Yangtao Li <[email protected]>
  9. */
  10. #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
  11. #include <linux/module.h>
  12. #include <linux/nvmem-consumer.h>
  13. #include <linux/of_device.h>
  14. #include <linux/platform_device.h>
  15. #include <linux/pm_opp.h>
  16. #include <linux/slab.h>
  17. #define MAX_NAME_LEN 7
  18. #define NVMEM_MASK 0x7
  19. #define NVMEM_SHIFT 5
  20. static struct platform_device *cpufreq_dt_pdev, *sun50i_cpufreq_pdev;
  21. /**
  22. * sun50i_cpufreq_get_efuse() - Determine speed grade from efuse value
  23. * @versions: Set to the value parsed from efuse
  24. *
  25. * Returns 0 if success.
  26. */
  27. static int sun50i_cpufreq_get_efuse(u32 *versions)
  28. {
  29. struct nvmem_cell *speedbin_nvmem;
  30. struct device_node *np;
  31. struct device *cpu_dev;
  32. u32 *speedbin, efuse_value;
  33. size_t len;
  34. int ret;
  35. cpu_dev = get_cpu_device(0);
  36. if (!cpu_dev)
  37. return -ENODEV;
  38. np = dev_pm_opp_of_get_opp_desc_node(cpu_dev);
  39. if (!np)
  40. return -ENOENT;
  41. ret = of_device_is_compatible(np,
  42. "allwinner,sun50i-h6-operating-points");
  43. if (!ret) {
  44. of_node_put(np);
  45. return -ENOENT;
  46. }
  47. speedbin_nvmem = of_nvmem_cell_get(np, NULL);
  48. of_node_put(np);
  49. if (IS_ERR(speedbin_nvmem))
  50. return dev_err_probe(cpu_dev, PTR_ERR(speedbin_nvmem),
  51. "Could not get nvmem cell\n");
  52. speedbin = nvmem_cell_read(speedbin_nvmem, &len);
  53. nvmem_cell_put(speedbin_nvmem);
  54. if (IS_ERR(speedbin))
  55. return PTR_ERR(speedbin);
  56. efuse_value = (*speedbin >> NVMEM_SHIFT) & NVMEM_MASK;
  57. /*
  58. * We treat unexpected efuse values as if the SoC was from
  59. * the slowest bin. Expected efuse values are 1-3, slowest
  60. * to fastest.
  61. */
  62. if (efuse_value >= 1 && efuse_value <= 3)
  63. *versions = efuse_value - 1;
  64. else
  65. *versions = 0;
  66. kfree(speedbin);
  67. return 0;
  68. };
  69. static int sun50i_cpufreq_nvmem_probe(struct platform_device *pdev)
  70. {
  71. int *opp_tokens;
  72. char name[MAX_NAME_LEN];
  73. unsigned int cpu;
  74. u32 speed = 0;
  75. int ret;
  76. opp_tokens = kcalloc(num_possible_cpus(), sizeof(*opp_tokens),
  77. GFP_KERNEL);
  78. if (!opp_tokens)
  79. return -ENOMEM;
  80. ret = sun50i_cpufreq_get_efuse(&speed);
  81. if (ret) {
  82. kfree(opp_tokens);
  83. return ret;
  84. }
  85. snprintf(name, MAX_NAME_LEN, "speed%d", speed);
  86. for_each_possible_cpu(cpu) {
  87. struct device *cpu_dev = get_cpu_device(cpu);
  88. if (!cpu_dev) {
  89. ret = -ENODEV;
  90. goto free_opp;
  91. }
  92. opp_tokens[cpu] = dev_pm_opp_set_prop_name(cpu_dev, name);
  93. if (opp_tokens[cpu] < 0) {
  94. ret = opp_tokens[cpu];
  95. pr_err("Failed to set prop name\n");
  96. goto free_opp;
  97. }
  98. }
  99. cpufreq_dt_pdev = platform_device_register_simple("cpufreq-dt", -1,
  100. NULL, 0);
  101. if (!IS_ERR(cpufreq_dt_pdev)) {
  102. platform_set_drvdata(pdev, opp_tokens);
  103. return 0;
  104. }
  105. ret = PTR_ERR(cpufreq_dt_pdev);
  106. pr_err("Failed to register platform device\n");
  107. free_opp:
  108. for_each_possible_cpu(cpu)
  109. dev_pm_opp_put_prop_name(opp_tokens[cpu]);
  110. kfree(opp_tokens);
  111. return ret;
  112. }
  113. static int sun50i_cpufreq_nvmem_remove(struct platform_device *pdev)
  114. {
  115. int *opp_tokens = platform_get_drvdata(pdev);
  116. unsigned int cpu;
  117. platform_device_unregister(cpufreq_dt_pdev);
  118. for_each_possible_cpu(cpu)
  119. dev_pm_opp_put_prop_name(opp_tokens[cpu]);
  120. kfree(opp_tokens);
  121. return 0;
  122. }
  123. static struct platform_driver sun50i_cpufreq_driver = {
  124. .probe = sun50i_cpufreq_nvmem_probe,
  125. .remove = sun50i_cpufreq_nvmem_remove,
  126. .driver = {
  127. .name = "sun50i-cpufreq-nvmem",
  128. },
  129. };
  130. static const struct of_device_id sun50i_cpufreq_match_list[] = {
  131. { .compatible = "allwinner,sun50i-h6" },
  132. {}
  133. };
  134. MODULE_DEVICE_TABLE(of, sun50i_cpufreq_match_list);
  135. static const struct of_device_id *sun50i_cpufreq_match_node(void)
  136. {
  137. const struct of_device_id *match;
  138. struct device_node *np;
  139. np = of_find_node_by_path("/");
  140. match = of_match_node(sun50i_cpufreq_match_list, np);
  141. of_node_put(np);
  142. return match;
  143. }
  144. /*
  145. * Since the driver depends on nvmem drivers, which may return EPROBE_DEFER,
  146. * all the real activity is done in the probe, which may be defered as well.
  147. * The init here is only registering the driver and the platform device.
  148. */
  149. static int __init sun50i_cpufreq_init(void)
  150. {
  151. const struct of_device_id *match;
  152. int ret;
  153. match = sun50i_cpufreq_match_node();
  154. if (!match)
  155. return -ENODEV;
  156. ret = platform_driver_register(&sun50i_cpufreq_driver);
  157. if (unlikely(ret < 0))
  158. return ret;
  159. sun50i_cpufreq_pdev =
  160. platform_device_register_simple("sun50i-cpufreq-nvmem",
  161. -1, NULL, 0);
  162. ret = PTR_ERR_OR_ZERO(sun50i_cpufreq_pdev);
  163. if (ret == 0)
  164. return 0;
  165. platform_driver_unregister(&sun50i_cpufreq_driver);
  166. return ret;
  167. }
  168. module_init(sun50i_cpufreq_init);
  169. static void __exit sun50i_cpufreq_exit(void)
  170. {
  171. platform_device_unregister(sun50i_cpufreq_pdev);
  172. platform_driver_unregister(&sun50i_cpufreq_driver);
  173. }
  174. module_exit(sun50i_cpufreq_exit);
  175. MODULE_DESCRIPTION("Sun50i-h6 cpufreq driver");
  176. MODULE_LICENSE("GPL v2");