sun50i-cpufreq-nvmem.c 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222
  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 <tiny.windzz@gmail.com>
  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. if (PTR_ERR(speedbin_nvmem) != -EPROBE_DEFER)
  51. pr_err("Could not get nvmem cell: %ld\n",
  52. PTR_ERR(speedbin_nvmem));
  53. return PTR_ERR(speedbin_nvmem);
  54. }
  55. speedbin = nvmem_cell_read(speedbin_nvmem, &len);
  56. nvmem_cell_put(speedbin_nvmem);
  57. if (IS_ERR(speedbin))
  58. return PTR_ERR(speedbin);
  59. efuse_value = (*speedbin >> NVMEM_SHIFT) & NVMEM_MASK;
  60. /*
  61. * We treat unexpected efuse values as if the SoC was from
  62. * the slowest bin. Expected efuse values are 1-3, slowest
  63. * to fastest.
  64. */
  65. if (efuse_value >= 1 && efuse_value <= 3)
  66. *versions = efuse_value - 1;
  67. else
  68. *versions = 0;
  69. kfree(speedbin);
  70. return 0;
  71. };
  72. static int sun50i_cpufreq_nvmem_probe(struct platform_device *pdev)
  73. {
  74. struct opp_table **opp_tables;
  75. char name[MAX_NAME_LEN];
  76. unsigned int cpu;
  77. u32 speed = 0;
  78. int ret;
  79. opp_tables = kcalloc(num_possible_cpus(), sizeof(*opp_tables),
  80. GFP_KERNEL);
  81. if (!opp_tables)
  82. return -ENOMEM;
  83. ret = sun50i_cpufreq_get_efuse(&speed);
  84. if (ret)
  85. return ret;
  86. snprintf(name, MAX_NAME_LEN, "speed%d", speed);
  87. for_each_possible_cpu(cpu) {
  88. struct device *cpu_dev = get_cpu_device(cpu);
  89. if (!cpu_dev) {
  90. ret = -ENODEV;
  91. goto free_opp;
  92. }
  93. opp_tables[cpu] = dev_pm_opp_set_prop_name(cpu_dev, name);
  94. if (IS_ERR(opp_tables[cpu])) {
  95. ret = PTR_ERR(opp_tables[cpu]);
  96. pr_err("Failed to set prop name\n");
  97. goto free_opp;
  98. }
  99. }
  100. cpufreq_dt_pdev = platform_device_register_simple("cpufreq-dt", -1,
  101. NULL, 0);
  102. if (!IS_ERR(cpufreq_dt_pdev)) {
  103. platform_set_drvdata(pdev, opp_tables);
  104. return 0;
  105. }
  106. ret = PTR_ERR(cpufreq_dt_pdev);
  107. pr_err("Failed to register platform device\n");
  108. free_opp:
  109. for_each_possible_cpu(cpu) {
  110. if (IS_ERR_OR_NULL(opp_tables[cpu]))
  111. break;
  112. dev_pm_opp_put_prop_name(opp_tables[cpu]);
  113. }
  114. kfree(opp_tables);
  115. return ret;
  116. }
  117. static int sun50i_cpufreq_nvmem_remove(struct platform_device *pdev)
  118. {
  119. struct opp_table **opp_tables = platform_get_drvdata(pdev);
  120. unsigned int cpu;
  121. platform_device_unregister(cpufreq_dt_pdev);
  122. for_each_possible_cpu(cpu)
  123. dev_pm_opp_put_prop_name(opp_tables[cpu]);
  124. kfree(opp_tables);
  125. return 0;
  126. }
  127. static struct platform_driver sun50i_cpufreq_driver = {
  128. .probe = sun50i_cpufreq_nvmem_probe,
  129. .remove = sun50i_cpufreq_nvmem_remove,
  130. .driver = {
  131. .name = "sun50i-cpufreq-nvmem",
  132. },
  133. };
  134. static const struct of_device_id sun50i_cpufreq_match_list[] = {
  135. { .compatible = "allwinner,sun50i-h6" },
  136. {}
  137. };
  138. MODULE_DEVICE_TABLE(of, sun50i_cpufreq_match_list);
  139. static const struct of_device_id *sun50i_cpufreq_match_node(void)
  140. {
  141. const struct of_device_id *match;
  142. struct device_node *np;
  143. np = of_find_node_by_path("/");
  144. match = of_match_node(sun50i_cpufreq_match_list, np);
  145. of_node_put(np);
  146. return match;
  147. }
  148. /*
  149. * Since the driver depends on nvmem drivers, which may return EPROBE_DEFER,
  150. * all the real activity is done in the probe, which may be defered as well.
  151. * The init here is only registering the driver and the platform device.
  152. */
  153. static int __init sun50i_cpufreq_init(void)
  154. {
  155. const struct of_device_id *match;
  156. int ret;
  157. match = sun50i_cpufreq_match_node();
  158. if (!match)
  159. return -ENODEV;
  160. ret = platform_driver_register(&sun50i_cpufreq_driver);
  161. if (unlikely(ret < 0))
  162. return ret;
  163. sun50i_cpufreq_pdev =
  164. platform_device_register_simple("sun50i-cpufreq-nvmem",
  165. -1, NULL, 0);
  166. ret = PTR_ERR_OR_ZERO(sun50i_cpufreq_pdev);
  167. if (ret == 0)
  168. return 0;
  169. platform_driver_unregister(&sun50i_cpufreq_driver);
  170. return ret;
  171. }
  172. module_init(sun50i_cpufreq_init);
  173. static void __exit sun50i_cpufreq_exit(void)
  174. {
  175. platform_device_unregister(sun50i_cpufreq_pdev);
  176. platform_driver_unregister(&sun50i_cpufreq_driver);
  177. }
  178. module_exit(sun50i_cpufreq_exit);
  179. MODULE_DESCRIPTION("Sun50i-h6 cpufreq driver");
  180. MODULE_LICENSE("GPL v2");