Commit | Line | Data |
---|---|---|
46e2856b IL |
1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* | |
3 | * Copyright (c) 2018, The Linux Foundation. All rights reserved. | |
4 | */ | |
5 | ||
6 | /* | |
7 | * In Certain QCOM SoCs like apq8096 and msm8996 that have KRYO processors, | |
8 | * the CPU frequency subset and voltage value of each OPP varies | |
9 | * based on the silicon variant in use. Qualcomm Process Voltage Scaling Tables | |
10 | * defines the voltage and frequency value based on the msm-id in SMEM | |
11 | * and speedbin blown in the efuse combination. | |
12 | * The qcom-cpufreq-kryo driver reads the msm-id and efuse value from the SoC | |
13 | * to provide the OPP framework with required information. | |
14 | * This is used to determine the voltage and frequency value for each OPP of | |
15 | * operating-points-v2 table when it is parsed by the OPP framework. | |
16 | */ | |
17 | ||
18 | #include <linux/cpu.h> | |
19 | #include <linux/err.h> | |
20 | #include <linux/init.h> | |
21 | #include <linux/kernel.h> | |
22 | #include <linux/module.h> | |
23 | #include <linux/nvmem-consumer.h> | |
24 | #include <linux/of.h> | |
25 | #include <linux/platform_device.h> | |
26 | #include <linux/pm_opp.h> | |
27 | #include <linux/slab.h> | |
28 | #include <linux/soc/qcom/smem.h> | |
29 | ||
30 | #define MSM_ID_SMEM 137 | |
31 | ||
32 | enum _msm_id { | |
33 | MSM8996V3 = 0xF6ul, | |
34 | APQ8096V3 = 0x123ul, | |
35 | MSM8996SG = 0x131ul, | |
36 | APQ8096SG = 0x138ul, | |
37 | }; | |
38 | ||
39 | enum _msm8996_version { | |
40 | MSM8996_V3, | |
41 | MSM8996_SG, | |
42 | NUM_OF_MSM8996_VERSIONS, | |
43 | }; | |
44 | ||
5ad7346b IL |
45 | struct platform_device *cpufreq_dt_pdev, *kryo_cpufreq_pdev; |
46 | ||
d51aea13 | 47 | static enum _msm8996_version qcom_cpufreq_kryo_get_msm_id(void) |
46e2856b IL |
48 | { |
49 | size_t len; | |
50 | u32 *msm_id; | |
51 | enum _msm8996_version version; | |
52 | ||
53 | msm_id = qcom_smem_get(QCOM_SMEM_HOST_ANY, MSM_ID_SMEM, &len); | |
54 | if (IS_ERR(msm_id)) | |
55 | return NUM_OF_MSM8996_VERSIONS; | |
56 | ||
57 | /* The first 4 bytes are format, next to them is the actual msm-id */ | |
58 | msm_id++; | |
59 | ||
60 | switch ((enum _msm_id)*msm_id) { | |
61 | case MSM8996V3: | |
62 | case APQ8096V3: | |
63 | version = MSM8996_V3; | |
64 | break; | |
65 | case MSM8996SG: | |
66 | case APQ8096SG: | |
67 | version = MSM8996_SG; | |
68 | break; | |
69 | default: | |
70 | version = NUM_OF_MSM8996_VERSIONS; | |
71 | } | |
72 | ||
73 | return version; | |
74 | } | |
75 | ||
76 | static int qcom_cpufreq_kryo_probe(struct platform_device *pdev) | |
77 | { | |
78 | struct opp_table *opp_tables[NR_CPUS] = {0}; | |
46e2856b IL |
79 | enum _msm8996_version msm8996_version; |
80 | struct nvmem_cell *speedbin_nvmem; | |
81 | struct device_node *np; | |
82 | struct device *cpu_dev; | |
83 | unsigned cpu; | |
84 | u8 *speedbin; | |
85 | u32 versions; | |
86 | size_t len; | |
87 | int ret; | |
88 | ||
89 | cpu_dev = get_cpu_device(0); | |
1dd2058f DC |
90 | if (!cpu_dev) |
91 | return -ENODEV; | |
46e2856b IL |
92 | |
93 | msm8996_version = qcom_cpufreq_kryo_get_msm_id(); | |
94 | if (NUM_OF_MSM8996_VERSIONS == msm8996_version) { | |
95 | dev_err(cpu_dev, "Not Snapdragon 820/821!"); | |
96 | return -ENODEV; | |
97 | } | |
98 | ||
99 | np = dev_pm_opp_of_get_opp_desc_node(cpu_dev); | |
1dd2058f DC |
100 | if (!np) |
101 | return -ENOENT; | |
46e2856b IL |
102 | |
103 | ret = of_device_is_compatible(np, "operating-points-v2-kryo-cpu"); | |
104 | if (!ret) { | |
105 | of_node_put(np); | |
106 | return -ENOENT; | |
107 | } | |
108 | ||
109 | speedbin_nvmem = of_nvmem_cell_get(np, NULL); | |
110 | of_node_put(np); | |
111 | if (IS_ERR(speedbin_nvmem)) { | |
f54ab690 NC |
112 | if (PTR_ERR(speedbin_nvmem) != -EPROBE_DEFER) |
113 | dev_err(cpu_dev, "Could not get nvmem cell: %ld\n", | |
114 | PTR_ERR(speedbin_nvmem)); | |
46e2856b IL |
115 | return PTR_ERR(speedbin_nvmem); |
116 | } | |
117 | ||
118 | speedbin = nvmem_cell_read(speedbin_nvmem, &len); | |
119 | nvmem_cell_put(speedbin_nvmem); | |
ee3dbcf9 IL |
120 | if (IS_ERR(speedbin)) |
121 | return PTR_ERR(speedbin); | |
46e2856b IL |
122 | |
123 | switch (msm8996_version) { | |
124 | case MSM8996_V3: | |
125 | versions = 1 << (unsigned int)(*speedbin); | |
126 | break; | |
127 | case MSM8996_SG: | |
128 | versions = 1 << ((unsigned int)(*speedbin) + 4); | |
129 | break; | |
130 | default: | |
131 | BUG(); | |
132 | break; | |
133 | } | |
ee3dbcf9 | 134 | kfree(speedbin); |
46e2856b IL |
135 | |
136 | for_each_possible_cpu(cpu) { | |
137 | cpu_dev = get_cpu_device(cpu); | |
138 | if (NULL == cpu_dev) { | |
139 | ret = -ENODEV; | |
140 | goto free_opp; | |
141 | } | |
142 | ||
143 | opp_tables[cpu] = dev_pm_opp_set_supported_hw(cpu_dev, | |
144 | &versions, 1); | |
145 | if (IS_ERR(opp_tables[cpu])) { | |
146 | ret = PTR_ERR(opp_tables[cpu]); | |
147 | dev_err(cpu_dev, "Failed to set supported hardware\n"); | |
148 | goto free_opp; | |
149 | } | |
150 | } | |
151 | ||
152 | cpufreq_dt_pdev = platform_device_register_simple("cpufreq-dt", -1, | |
153 | NULL, 0); | |
154 | if (!IS_ERR(cpufreq_dt_pdev)) | |
155 | return 0; | |
156 | ||
157 | ret = PTR_ERR(cpufreq_dt_pdev); | |
158 | dev_err(cpu_dev, "Failed to register platform device\n"); | |
159 | ||
160 | free_opp: | |
161 | for_each_possible_cpu(cpu) { | |
162 | if (IS_ERR_OR_NULL(opp_tables[cpu])) | |
163 | break; | |
164 | dev_pm_opp_put_supported_hw(opp_tables[cpu]); | |
165 | } | |
166 | ||
167 | return ret; | |
168 | } | |
169 | ||
5ad7346b IL |
170 | static int qcom_cpufreq_kryo_remove(struct platform_device *pdev) |
171 | { | |
172 | platform_device_unregister(cpufreq_dt_pdev); | |
173 | return 0; | |
174 | } | |
175 | ||
46e2856b IL |
176 | static struct platform_driver qcom_cpufreq_kryo_driver = { |
177 | .probe = qcom_cpufreq_kryo_probe, | |
5ad7346b | 178 | .remove = qcom_cpufreq_kryo_remove, |
46e2856b IL |
179 | .driver = { |
180 | .name = "qcom-cpufreq-kryo", | |
181 | }, | |
182 | }; | |
183 | ||
184 | static const struct of_device_id qcom_cpufreq_kryo_match_list[] __initconst = { | |
185 | { .compatible = "qcom,apq8096", }, | |
186 | { .compatible = "qcom,msm8996", }, | |
bafaf056 | 187 | {} |
46e2856b IL |
188 | }; |
189 | ||
190 | /* | |
191 | * Since the driver depends on smem and nvmem drivers, which may | |
192 | * return EPROBE_DEFER, all the real activity is done in the probe, | |
193 | * which may be defered as well. The init here is only registering | |
194 | * the driver and the platform device. | |
195 | */ | |
196 | static int __init qcom_cpufreq_kryo_init(void) | |
197 | { | |
198 | struct device_node *np = of_find_node_by_path("/"); | |
199 | const struct of_device_id *match; | |
200 | int ret; | |
201 | ||
202 | if (!np) | |
203 | return -ENODEV; | |
204 | ||
205 | match = of_match_node(qcom_cpufreq_kryo_match_list, np); | |
206 | of_node_put(np); | |
207 | if (!match) | |
208 | return -ENODEV; | |
209 | ||
210 | ret = platform_driver_register(&qcom_cpufreq_kryo_driver); | |
211 | if (unlikely(ret < 0)) | |
212 | return ret; | |
213 | ||
5ad7346b IL |
214 | kryo_cpufreq_pdev = platform_device_register_simple( |
215 | "qcom-cpufreq-kryo", -1, NULL, 0); | |
216 | ret = PTR_ERR_OR_ZERO(kryo_cpufreq_pdev); | |
46e2856b IL |
217 | if (0 == ret) |
218 | return 0; | |
219 | ||
220 | platform_driver_unregister(&qcom_cpufreq_kryo_driver); | |
221 | return ret; | |
222 | } | |
223 | module_init(qcom_cpufreq_kryo_init); | |
224 | ||
d51aea13 | 225 | static void __exit qcom_cpufreq_kryo_exit(void) |
5ad7346b IL |
226 | { |
227 | platform_device_unregister(kryo_cpufreq_pdev); | |
228 | platform_driver_unregister(&qcom_cpufreq_kryo_driver); | |
229 | } | |
230 | module_exit(qcom_cpufreq_kryo_exit); | |
231 | ||
46e2856b IL |
232 | MODULE_DESCRIPTION("Qualcomm Technologies, Inc. Kryo CPUfreq driver"); |
233 | MODULE_LICENSE("GPL v2"); |