1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * OMAP3/OMAP4 Voltage Management Routines |
4 | * |
5 | * Author: Thara Gopinath <thara@ti.com> |
6 | * |
7 | * Copyright (C) 2007 Texas Instruments, Inc. |
8 | * Rajendra Nayak <rnayak@ti.com> |
9 | * Lesly A M <x0080970@ti.com> |
10 | * |
11 | * Copyright (C) 2008, 2011 Nokia Corporation |
12 | * Kalle Jokiniemi |
13 | * Paul Walmsley |
14 | * |
15 | * Copyright (C) 2010 Texas Instruments, Inc. |
16 | * Thara Gopinath <thara@ti.com> |
17 | */ |
18 | |
19 | #include <linux/delay.h> |
20 | #include <linux/io.h> |
21 | #include <linux/err.h> |
22 | #include <linux/export.h> |
23 | #include <linux/debugfs.h> |
24 | #include <linux/slab.h> |
25 | #include <linux/clk.h> |
26 | |
27 | #include "common.h" |
28 | |
29 | #include "prm-regbits-34xx.h" |
30 | #include "prm-regbits-44xx.h" |
31 | #include "prm44xx.h" |
32 | #include "prcm44xx.h" |
33 | #include "prminst44xx.h" |
34 | #include "control.h" |
35 | |
36 | #include "voltage.h" |
37 | #include "powerdomain.h" |
38 | |
39 | #include "vc.h" |
40 | #include "vp.h" |
41 | |
42 | static LIST_HEAD(voltdm_list); |
43 | |
44 | /* Public functions */ |
45 | /** |
46 | * voltdm_get_voltage() - Gets the current non-auto-compensated voltage |
47 | * @voltdm: pointer to the voltdm for which current voltage info is needed |
48 | * |
49 | * API to get the current non-auto-compensated voltage for a voltage domain. |
50 | * Returns 0 in case of error else returns the current voltage. |
51 | */ |
52 | unsigned long voltdm_get_voltage(struct voltagedomain *voltdm) |
53 | { |
54 | if (!voltdm || IS_ERR(ptr: voltdm)) { |
55 | pr_warn("%s: VDD specified does not exist!\n" , __func__); |
56 | return 0; |
57 | } |
58 | |
59 | return voltdm->nominal_volt; |
60 | } |
61 | |
62 | /** |
63 | * voltdm_scale() - API to scale voltage of a particular voltage domain. |
64 | * @voltdm: pointer to the voltage domain which is to be scaled. |
65 | * @target_volt: The target voltage of the voltage domain |
66 | * |
67 | * This API should be called by the kernel to do the voltage scaling |
68 | * for a particular voltage domain during DVFS. |
69 | */ |
70 | static int voltdm_scale(struct voltagedomain *voltdm, |
71 | unsigned long target_volt) |
72 | { |
73 | int ret, i; |
74 | unsigned long volt = 0; |
75 | |
76 | if (!voltdm || IS_ERR(ptr: voltdm)) { |
77 | pr_warn("%s: VDD specified does not exist!\n" , __func__); |
78 | return -EINVAL; |
79 | } |
80 | |
81 | if (!voltdm->scale) { |
82 | pr_err("%s: No voltage scale API registered for vdd_%s\n" , |
83 | __func__, voltdm->name); |
84 | return -ENODATA; |
85 | } |
86 | |
87 | if (!voltdm->volt_data) { |
88 | pr_err("%s: No voltage data defined for vdd_%s\n" , |
89 | __func__, voltdm->name); |
90 | return -ENODATA; |
91 | } |
92 | |
93 | /* Adjust voltage to the exact voltage from the OPP table */ |
94 | for (i = 0; voltdm->volt_data[i].volt_nominal != 0; i++) { |
95 | if (voltdm->volt_data[i].volt_nominal >= target_volt) { |
96 | volt = voltdm->volt_data[i].volt_nominal; |
97 | break; |
98 | } |
99 | } |
100 | |
101 | if (!volt) { |
102 | pr_warn("%s: not scaling. OPP voltage for %lu, not found.\n" , |
103 | __func__, target_volt); |
104 | return -EINVAL; |
105 | } |
106 | |
107 | ret = voltdm->scale(voltdm, volt); |
108 | if (!ret) |
109 | voltdm->nominal_volt = volt; |
110 | |
111 | return ret; |
112 | } |
113 | |
114 | /** |
115 | * voltdm_reset() - Resets the voltage of a particular voltage domain |
116 | * to that of the current OPP. |
117 | * @voltdm: pointer to the voltage domain whose voltage is to be reset. |
118 | * |
119 | * This API finds out the correct voltage the voltage domain is supposed |
120 | * to be at and resets the voltage to that level. Should be used especially |
121 | * while disabling any voltage compensation modules. |
122 | */ |
123 | void voltdm_reset(struct voltagedomain *voltdm) |
124 | { |
125 | unsigned long target_volt; |
126 | |
127 | if (!voltdm || IS_ERR(ptr: voltdm)) { |
128 | pr_warn("%s: VDD specified does not exist!\n" , __func__); |
129 | return; |
130 | } |
131 | |
132 | target_volt = voltdm_get_voltage(voltdm); |
133 | if (!target_volt) { |
134 | pr_err("%s: unable to find current voltage for vdd_%s\n" , |
135 | __func__, voltdm->name); |
136 | return; |
137 | } |
138 | |
139 | voltdm_scale(voltdm, target_volt); |
140 | } |
141 | |
142 | /** |
143 | * omap_voltage_get_volttable() - API to get the voltage table associated with a |
144 | * particular voltage domain. |
145 | * @voltdm: pointer to the VDD for which the voltage table is required |
146 | * @volt_data: the voltage table for the particular vdd which is to be |
147 | * populated by this API |
148 | * |
149 | * This API populates the voltage table associated with a VDD into the |
150 | * passed parameter pointer. Returns the count of distinct voltages |
151 | * supported by this vdd. |
152 | * |
153 | */ |
154 | void omap_voltage_get_volttable(struct voltagedomain *voltdm, |
155 | struct omap_volt_data **volt_data) |
156 | { |
157 | if (!voltdm || IS_ERR(ptr: voltdm)) { |
158 | pr_warn("%s: VDD specified does not exist!\n" , __func__); |
159 | return; |
160 | } |
161 | |
162 | *volt_data = voltdm->volt_data; |
163 | } |
164 | |
165 | /** |
166 | * omap_voltage_get_voltdata() - API to get the voltage table entry for a |
167 | * particular voltage |
168 | * @voltdm: pointer to the VDD whose voltage table has to be searched |
169 | * @volt: the voltage to be searched in the voltage table |
170 | * |
171 | * This API searches through the voltage table for the required voltage |
172 | * domain and tries to find a matching entry for the passed voltage volt. |
173 | * If a matching entry is found volt_data is populated with that entry. |
174 | * This API searches only through the non-compensated voltages int the |
175 | * voltage table. |
176 | * Returns pointer to the voltage table entry corresponding to volt on |
177 | * success. Returns -ENODATA if no voltage table exisits for the passed voltage |
178 | * domain or if there is no matching entry. |
179 | */ |
180 | struct omap_volt_data *omap_voltage_get_voltdata(struct voltagedomain *voltdm, |
181 | unsigned long volt) |
182 | { |
183 | int i; |
184 | |
185 | if (!voltdm || IS_ERR(ptr: voltdm)) { |
186 | pr_warn("%s: VDD specified does not exist!\n" , __func__); |
187 | return ERR_PTR(error: -EINVAL); |
188 | } |
189 | |
190 | if (!voltdm->volt_data) { |
191 | pr_warn("%s: voltage table does not exist for vdd_%s\n" , |
192 | __func__, voltdm->name); |
193 | return ERR_PTR(error: -ENODATA); |
194 | } |
195 | |
196 | for (i = 0; voltdm->volt_data[i].volt_nominal != 0; i++) { |
197 | if (voltdm->volt_data[i].volt_nominal == volt) |
198 | return &voltdm->volt_data[i]; |
199 | } |
200 | |
201 | pr_notice("%s: Unable to match the current voltage with the voltage table for vdd_%s\n" , |
202 | __func__, voltdm->name); |
203 | |
204 | return ERR_PTR(error: -ENODATA); |
205 | } |
206 | |
207 | /** |
208 | * omap_voltage_register_pmic() - API to register PMIC specific data |
209 | * @voltdm: pointer to the VDD for which the PMIC specific data is |
210 | * to be registered |
211 | * @pmic: the structure containing pmic info |
212 | * |
213 | * This API is to be called by the SOC/PMIC file to specify the |
214 | * pmic specific info as present in omap_voltdm_pmic structure. |
215 | */ |
216 | int omap_voltage_register_pmic(struct voltagedomain *voltdm, |
217 | struct omap_voltdm_pmic *pmic) |
218 | { |
219 | if (!voltdm || IS_ERR(ptr: voltdm)) { |
220 | pr_warn("%s: VDD specified does not exist!\n" , __func__); |
221 | return -EINVAL; |
222 | } |
223 | |
224 | voltdm->pmic = pmic; |
225 | |
226 | return 0; |
227 | } |
228 | |
229 | /** |
230 | * omap_voltage_late_init() - Init the various voltage parameters |
231 | * |
232 | * This API is to be called in the later stages of the |
233 | * system boot to init the voltage controller and |
234 | * voltage processors. |
235 | */ |
236 | int __init omap_voltage_late_init(void) |
237 | { |
238 | struct voltagedomain *voltdm; |
239 | |
240 | if (list_empty(head: &voltdm_list)) { |
241 | pr_err("%s: Voltage driver support not added\n" , |
242 | __func__); |
243 | return -EINVAL; |
244 | } |
245 | |
246 | list_for_each_entry(voltdm, &voltdm_list, node) { |
247 | struct clk *sys_ck; |
248 | |
249 | if (!voltdm->scalable) |
250 | continue; |
251 | |
252 | sys_ck = clk_get(NULL, id: voltdm->sys_clk.name); |
253 | if (IS_ERR(ptr: sys_ck)) { |
254 | pr_warn("%s: Could not get sys clk.\n" , __func__); |
255 | return -EINVAL; |
256 | } |
257 | voltdm->sys_clk.rate = clk_get_rate(clk: sys_ck); |
258 | WARN_ON(!voltdm->sys_clk.rate); |
259 | clk_put(clk: sys_ck); |
260 | |
261 | if (voltdm->vc) { |
262 | voltdm->scale = omap_vc_bypass_scale; |
263 | omap_vc_init_channel(voltdm); |
264 | } |
265 | |
266 | if (voltdm->vp) { |
267 | voltdm->scale = omap_vp_forceupdate_scale; |
268 | omap_vp_init(voltdm); |
269 | } |
270 | } |
271 | |
272 | return 0; |
273 | } |
274 | |
275 | static struct voltagedomain *_voltdm_lookup(const char *name) |
276 | { |
277 | struct voltagedomain *voltdm, *temp_voltdm; |
278 | |
279 | voltdm = NULL; |
280 | |
281 | list_for_each_entry(temp_voltdm, &voltdm_list, node) { |
282 | if (!strcmp(name, temp_voltdm->name)) { |
283 | voltdm = temp_voltdm; |
284 | break; |
285 | } |
286 | } |
287 | |
288 | return voltdm; |
289 | } |
290 | |
291 | static int _voltdm_register(struct voltagedomain *voltdm) |
292 | { |
293 | if (!voltdm || !voltdm->name) |
294 | return -EINVAL; |
295 | |
296 | list_add(new: &voltdm->node, head: &voltdm_list); |
297 | |
298 | pr_debug("voltagedomain: registered %s\n" , voltdm->name); |
299 | |
300 | return 0; |
301 | } |
302 | |
303 | /** |
304 | * voltdm_lookup - look up a voltagedomain by name, return a pointer |
305 | * @name: name of voltagedomain |
306 | * |
307 | * Find a registered voltagedomain by its name @name. Returns a pointer |
308 | * to the struct voltagedomain if found, or NULL otherwise. |
309 | */ |
310 | struct voltagedomain *voltdm_lookup(const char *name) |
311 | { |
312 | struct voltagedomain *voltdm ; |
313 | |
314 | if (!name) |
315 | return NULL; |
316 | |
317 | voltdm = _voltdm_lookup(name); |
318 | |
319 | return voltdm; |
320 | } |
321 | |
322 | /** |
323 | * voltdm_init - set up the voltagedomain layer |
324 | * @voltdm_list: array of struct voltagedomain pointers to register |
325 | * |
326 | * Loop through the array of voltagedomains @voltdm_list, registering all |
327 | * that are available on the current CPU. If voltdm_list is supplied |
328 | * and not null, all of the referenced voltagedomains will be |
329 | * registered. No return value. |
330 | */ |
331 | void voltdm_init(struct voltagedomain **voltdms) |
332 | { |
333 | struct voltagedomain **v; |
334 | |
335 | if (voltdms) { |
336 | for (v = voltdms; *v; v++) |
337 | _voltdm_register(voltdm: *v); |
338 | } |
339 | } |
340 | |