Commit | Line | Data |
---|---|---|
2f34ce81 TG |
1 | /* |
2 | * OMAP3/OMAP4 Voltage Management Routines | |
3 | * | |
4 | * Author: Thara Gopinath <thara@ti.com> | |
5 | * | |
6 | * Copyright (C) 2007 Texas Instruments, Inc. | |
7 | * Rajendra Nayak <rnayak@ti.com> | |
8 | * Lesly A M <x0080970@ti.com> | |
9 | * | |
c0718df4 | 10 | * Copyright (C) 2008, 2011 Nokia Corporation |
2f34ce81 | 11 | * Kalle Jokiniemi |
c0718df4 | 12 | * Paul Walmsley |
2f34ce81 TG |
13 | * |
14 | * Copyright (C) 2010 Texas Instruments, Inc. | |
15 | * Thara Gopinath <thara@ti.com> | |
16 | * | |
17 | * This program is free software; you can redistribute it and/or modify | |
18 | * it under the terms of the GNU General Public License version 2 as | |
19 | * published by the Free Software Foundation. | |
20 | */ | |
21 | ||
22 | #include <linux/delay.h> | |
23 | #include <linux/io.h> | |
2f34ce81 TG |
24 | #include <linux/err.h> |
25 | #include <linux/debugfs.h> | |
26 | #include <linux/slab.h> | |
0e2f3d9c | 27 | #include <linux/clk.h> |
2f34ce81 TG |
28 | |
29 | #include <plat/common.h> | |
2f34ce81 TG |
30 | |
31 | #include "prm-regbits-34xx.h" | |
bd38107b TG |
32 | #include "prm-regbits-44xx.h" |
33 | #include "prm44xx.h" | |
34 | #include "prcm44xx.h" | |
35 | #include "prminst44xx.h" | |
2f34ce81 TG |
36 | #include "control.h" |
37 | ||
e1d6f472 | 38 | #include "voltage.h" |
e69c22b1 | 39 | #include "powerdomain.h" |
e1d6f472 | 40 | |
c0718df4 PW |
41 | #include "vc.h" |
42 | #include "vp.h" | |
43 | ||
81a60482 | 44 | static LIST_HEAD(voltdm_list); |
2f34ce81 | 45 | |
81a60482 | 46 | static int __init _config_common_vdd_data(struct voltagedomain *voltdm) |
c0718df4 | 47 | { |
81a60482 | 48 | struct omap_vdd_info *vdd = voltdm->vdd; |
c0718df4 PW |
49 | |
50 | /* Generic voltage parameters */ | |
01f48d30 | 51 | vdd->volt_scale = omap_vp_forceupdate_scale; |
c0718df4 PW |
52 | |
53 | return 0; | |
54 | } | |
55 | ||
81a60482 | 56 | static int __init omap_vdd_data_configure(struct voltagedomain *voltdm) |
bd38107b | 57 | { |
c0718df4 | 58 | int ret = -EINVAL; |
bd38107b | 59 | |
ce8ebe0d | 60 | if (!voltdm->pmic) { |
bd38107b TG |
61 | pr_err("%s: PMIC info requried to configure vdd_%s not" |
62 | "populated.Hence cannot initialize vdd_%s\n", | |
81a60482 | 63 | __func__, voltdm->name, voltdm->name); |
c0718df4 | 64 | goto ovdc_out; |
bd38107b TG |
65 | } |
66 | ||
81a60482 | 67 | if (IS_ERR_VALUE(_config_common_vdd_data(voltdm))) |
c0718df4 | 68 | goto ovdc_out; |
bd38107b | 69 | |
4bcc475e | 70 | ret = 0; |
bd38107b | 71 | |
c0718df4 PW |
72 | ovdc_out: |
73 | return ret; | |
bd38107b TG |
74 | } |
75 | ||
2f34ce81 TG |
76 | /* Public functions */ |
77 | /** | |
78 | * omap_voltage_get_nom_volt() - Gets the current non-auto-compensated voltage | |
79 | * @voltdm: pointer to the VDD for which current voltage info is needed | |
80 | * | |
81 | * API to get the current non-auto-compensated voltage for a VDD. | |
82 | * Returns 0 in case of error else returns the current voltage for the VDD. | |
83 | */ | |
84 | unsigned long omap_voltage_get_nom_volt(struct voltagedomain *voltdm) | |
85 | { | |
86 | struct omap_vdd_info *vdd; | |
87 | ||
88 | if (!voltdm || IS_ERR(voltdm)) { | |
89 | pr_warning("%s: VDD specified does not exist!\n", __func__); | |
90 | return 0; | |
91 | } | |
92 | ||
81a60482 | 93 | vdd = voltdm->vdd; |
2f34ce81 TG |
94 | |
95 | return vdd->curr_volt; | |
96 | } | |
97 | ||
2f34ce81 TG |
98 | /** |
99 | * omap_voltage_scale_vdd() - API to scale voltage of a particular | |
100 | * voltage domain. | |
101 | * @voltdm: pointer to the VDD which is to be scaled. | |
102 | * @target_volt: The target voltage of the voltage domain | |
103 | * | |
104 | * This API should be called by the kernel to do the voltage scaling | |
105 | * for a particular voltage domain during dvfs or any other situation. | |
106 | */ | |
107 | int omap_voltage_scale_vdd(struct voltagedomain *voltdm, | |
108 | unsigned long target_volt) | |
109 | { | |
110 | struct omap_vdd_info *vdd; | |
111 | ||
112 | if (!voltdm || IS_ERR(voltdm)) { | |
113 | pr_warning("%s: VDD specified does not exist!\n", __func__); | |
114 | return -EINVAL; | |
115 | } | |
116 | ||
81a60482 | 117 | vdd = voltdm->vdd; |
2f34ce81 TG |
118 | |
119 | if (!vdd->volt_scale) { | |
120 | pr_err("%s: No voltage scale API registered for vdd_%s\n", | |
121 | __func__, voltdm->name); | |
122 | return -ENODATA; | |
123 | } | |
124 | ||
81a60482 | 125 | return vdd->volt_scale(voltdm, target_volt); |
2f34ce81 TG |
126 | } |
127 | ||
128 | /** | |
129 | * omap_voltage_reset() - Resets the voltage of a particular voltage domain | |
130 | * to that of the current OPP. | |
131 | * @voltdm: pointer to the VDD whose voltage is to be reset. | |
132 | * | |
133 | * This API finds out the correct voltage the voltage domain is supposed | |
25985edc | 134 | * to be at and resets the voltage to that level. Should be used especially |
2f34ce81 TG |
135 | * while disabling any voltage compensation modules. |
136 | */ | |
137 | void omap_voltage_reset(struct voltagedomain *voltdm) | |
138 | { | |
139 | unsigned long target_uvdc; | |
140 | ||
141 | if (!voltdm || IS_ERR(voltdm)) { | |
142 | pr_warning("%s: VDD specified does not exist!\n", __func__); | |
143 | return; | |
144 | } | |
145 | ||
146 | target_uvdc = omap_voltage_get_nom_volt(voltdm); | |
147 | if (!target_uvdc) { | |
148 | pr_err("%s: unable to find current voltage for vdd_%s\n", | |
149 | __func__, voltdm->name); | |
150 | return; | |
151 | } | |
152 | ||
153 | omap_voltage_scale_vdd(voltdm, target_uvdc); | |
154 | } | |
155 | ||
156 | /** | |
157 | * omap_voltage_get_volttable() - API to get the voltage table associated with a | |
158 | * particular voltage domain. | |
159 | * @voltdm: pointer to the VDD for which the voltage table is required | |
160 | * @volt_data: the voltage table for the particular vdd which is to be | |
161 | * populated by this API | |
162 | * | |
163 | * This API populates the voltage table associated with a VDD into the | |
164 | * passed parameter pointer. Returns the count of distinct voltages | |
165 | * supported by this vdd. | |
166 | * | |
167 | */ | |
168 | void omap_voltage_get_volttable(struct voltagedomain *voltdm, | |
169 | struct omap_volt_data **volt_data) | |
170 | { | |
171 | struct omap_vdd_info *vdd; | |
172 | ||
173 | if (!voltdm || IS_ERR(voltdm)) { | |
174 | pr_warning("%s: VDD specified does not exist!\n", __func__); | |
175 | return; | |
176 | } | |
177 | ||
81a60482 | 178 | vdd = voltdm->vdd; |
2f34ce81 TG |
179 | |
180 | *volt_data = vdd->volt_data; | |
181 | } | |
182 | ||
183 | /** | |
184 | * omap_voltage_get_voltdata() - API to get the voltage table entry for a | |
185 | * particular voltage | |
186 | * @voltdm: pointer to the VDD whose voltage table has to be searched | |
187 | * @volt: the voltage to be searched in the voltage table | |
188 | * | |
189 | * This API searches through the voltage table for the required voltage | |
190 | * domain and tries to find a matching entry for the passed voltage volt. | |
191 | * If a matching entry is found volt_data is populated with that entry. | |
192 | * This API searches only through the non-compensated voltages int the | |
193 | * voltage table. | |
194 | * Returns pointer to the voltage table entry corresponding to volt on | |
25985edc | 195 | * success. Returns -ENODATA if no voltage table exisits for the passed voltage |
2f34ce81 TG |
196 | * domain or if there is no matching entry. |
197 | */ | |
198 | struct omap_volt_data *omap_voltage_get_voltdata(struct voltagedomain *voltdm, | |
199 | unsigned long volt) | |
200 | { | |
201 | struct omap_vdd_info *vdd; | |
202 | int i; | |
203 | ||
204 | if (!voltdm || IS_ERR(voltdm)) { | |
205 | pr_warning("%s: VDD specified does not exist!\n", __func__); | |
206 | return ERR_PTR(-EINVAL); | |
207 | } | |
208 | ||
81a60482 | 209 | vdd = voltdm->vdd; |
2f34ce81 TG |
210 | |
211 | if (!vdd->volt_data) { | |
212 | pr_warning("%s: voltage table does not exist for vdd_%s\n", | |
213 | __func__, voltdm->name); | |
214 | return ERR_PTR(-ENODATA); | |
215 | } | |
216 | ||
217 | for (i = 0; vdd->volt_data[i].volt_nominal != 0; i++) { | |
218 | if (vdd->volt_data[i].volt_nominal == volt) | |
219 | return &vdd->volt_data[i]; | |
220 | } | |
221 | ||
222 | pr_notice("%s: Unable to match the current voltage with the voltage" | |
223 | "table for vdd_%s\n", __func__, voltdm->name); | |
224 | ||
225 | return ERR_PTR(-ENODATA); | |
226 | } | |
227 | ||
228 | /** | |
229 | * omap_voltage_register_pmic() - API to register PMIC specific data | |
230 | * @voltdm: pointer to the VDD for which the PMIC specific data is | |
231 | * to be registered | |
ce8ebe0d | 232 | * @pmic: the structure containing pmic info |
2f34ce81 TG |
233 | * |
234 | * This API is to be called by the SOC/PMIC file to specify the | |
ce8ebe0d | 235 | * pmic specific info as present in omap_voltdm_pmic structure. |
2f34ce81 TG |
236 | */ |
237 | int omap_voltage_register_pmic(struct voltagedomain *voltdm, | |
ce8ebe0d | 238 | struct omap_voltdm_pmic *pmic) |
2f34ce81 | 239 | { |
2f34ce81 TG |
240 | if (!voltdm || IS_ERR(voltdm)) { |
241 | pr_warning("%s: VDD specified does not exist!\n", __func__); | |
242 | return -EINVAL; | |
243 | } | |
244 | ||
ce8ebe0d | 245 | voltdm->pmic = pmic; |
2f34ce81 TG |
246 | |
247 | return 0; | |
248 | } | |
249 | ||
2f34ce81 TG |
250 | /** |
251 | * omap_change_voltscale_method() - API to change the voltage scaling method. | |
252 | * @voltdm: pointer to the VDD whose voltage scaling method | |
253 | * has to be changed. | |
254 | * @voltscale_method: the method to be used for voltage scaling. | |
255 | * | |
256 | * This API can be used by the board files to change the method of voltage | |
257 | * scaling between vpforceupdate and vcbypass. The parameter values are | |
258 | * defined in voltage.h | |
259 | */ | |
260 | void omap_change_voltscale_method(struct voltagedomain *voltdm, | |
261 | int voltscale_method) | |
262 | { | |
263 | struct omap_vdd_info *vdd; | |
264 | ||
265 | if (!voltdm || IS_ERR(voltdm)) { | |
266 | pr_warning("%s: VDD specified does not exist!\n", __func__); | |
267 | return; | |
268 | } | |
269 | ||
81a60482 | 270 | vdd = voltdm->vdd; |
2f34ce81 TG |
271 | |
272 | switch (voltscale_method) { | |
273 | case VOLTSCALE_VPFORCEUPDATE: | |
01f48d30 | 274 | vdd->volt_scale = omap_vp_forceupdate_scale; |
2f34ce81 TG |
275 | return; |
276 | case VOLTSCALE_VCBYPASS: | |
d84adcf4 | 277 | vdd->volt_scale = omap_vc_bypass_scale; |
2f34ce81 TG |
278 | return; |
279 | default: | |
280 | pr_warning("%s: Trying to change the method of voltage scaling" | |
281 | "to an unsupported one!\n", __func__); | |
282 | } | |
283 | } | |
284 | ||
2f34ce81 TG |
285 | /** |
286 | * omap_voltage_late_init() - Init the various voltage parameters | |
287 | * | |
288 | * This API is to be called in the later stages of the | |
289 | * system boot to init the voltage controller and | |
290 | * voltage processors. | |
291 | */ | |
292 | int __init omap_voltage_late_init(void) | |
293 | { | |
81a60482 | 294 | struct voltagedomain *voltdm; |
2f34ce81 | 295 | |
81a60482 | 296 | if (list_empty(&voltdm_list)) { |
2f34ce81 TG |
297 | pr_err("%s: Voltage driver support not added\n", |
298 | __func__); | |
299 | return -EINVAL; | |
300 | } | |
301 | ||
81a60482 | 302 | list_for_each_entry(voltdm, &voltdm_list, node) { |
0e2f3d9c KH |
303 | struct clk *sys_ck; |
304 | ||
37efca7e KH |
305 | if (!voltdm->scalable) |
306 | continue; | |
307 | ||
0e2f3d9c KH |
308 | sys_ck = clk_get(NULL, voltdm->sys_clk.name); |
309 | if (IS_ERR(sys_ck)) { | |
310 | pr_warning("%s: Could not get sys clk.\n", __func__); | |
311 | return -EINVAL; | |
312 | } | |
313 | voltdm->sys_clk.rate = clk_get_rate(sys_ck); | |
314 | WARN_ON(!voltdm->sys_clk.rate); | |
315 | clk_put(sys_ck); | |
316 | ||
4d47506a KH |
317 | if (voltdm->vc) { |
318 | voltdm->vdd->volt_scale = omap_vc_bypass_scale; | |
d84adcf4 | 319 | omap_vc_init_channel(voltdm); |
4d47506a | 320 | } |
d84adcf4 | 321 | |
81a60482 KH |
322 | if (voltdm->vdd) { |
323 | if (omap_vdd_data_configure(voltdm)) | |
324 | continue; | |
01f48d30 | 325 | omap_vp_init(voltdm); |
81a60482 | 326 | } |
2f34ce81 TG |
327 | } |
328 | ||
329 | return 0; | |
330 | } | |
331 | ||
81a60482 | 332 | static struct voltagedomain *_voltdm_lookup(const char *name) |
2f34ce81 | 333 | { |
81a60482 KH |
334 | struct voltagedomain *voltdm, *temp_voltdm; |
335 | ||
336 | voltdm = NULL; | |
337 | ||
338 | list_for_each_entry(temp_voltdm, &voltdm_list, node) { | |
339 | if (!strcmp(name, temp_voltdm->name)) { | |
340 | voltdm = temp_voltdm; | |
341 | break; | |
342 | } | |
343 | } | |
344 | ||
345 | return voltdm; | |
346 | } | |
347 | ||
e69c22b1 KH |
348 | /** |
349 | * voltdm_add_pwrdm - add a powerdomain to a voltagedomain | |
350 | * @voltdm: struct voltagedomain * to add the powerdomain to | |
351 | * @pwrdm: struct powerdomain * to associate with a voltagedomain | |
352 | * | |
353 | * Associate the powerdomain @pwrdm with a voltagedomain @voltdm. This | |
354 | * enables the use of voltdm_for_each_pwrdm(). Returns -EINVAL if | |
355 | * presented with invalid pointers; -ENOMEM if memory could not be allocated; | |
356 | * or 0 upon success. | |
357 | */ | |
358 | int voltdm_add_pwrdm(struct voltagedomain *voltdm, struct powerdomain *pwrdm) | |
359 | { | |
360 | if (!voltdm || !pwrdm) | |
361 | return -EINVAL; | |
362 | ||
363 | pr_debug("voltagedomain: associating powerdomain %s with voltagedomain " | |
364 | "%s\n", pwrdm->name, voltdm->name); | |
365 | ||
366 | list_add(&pwrdm->voltdm_node, &voltdm->pwrdm_list); | |
367 | ||
368 | return 0; | |
369 | } | |
370 | ||
371 | /** | |
372 | * voltdm_for_each_pwrdm - call function for each pwrdm in a voltdm | |
373 | * @voltdm: struct voltagedomain * to iterate over | |
374 | * @fn: callback function * | |
375 | * | |
376 | * Call the supplied function @fn for each powerdomain in the | |
377 | * voltagedomain @voltdm. Returns -EINVAL if presented with invalid | |
378 | * pointers; or passes along the last return value of the callback | |
379 | * function, which should be 0 for success or anything else to | |
380 | * indicate failure. | |
381 | */ | |
382 | int voltdm_for_each_pwrdm(struct voltagedomain *voltdm, | |
383 | int (*fn)(struct voltagedomain *voltdm, | |
384 | struct powerdomain *pwrdm)) | |
385 | { | |
386 | struct powerdomain *pwrdm; | |
387 | int ret = 0; | |
388 | ||
389 | if (!fn) | |
390 | return -EINVAL; | |
391 | ||
392 | list_for_each_entry(pwrdm, &voltdm->pwrdm_list, voltdm_node) | |
393 | ret = (*fn)(voltdm, pwrdm); | |
394 | ||
395 | return ret; | |
396 | } | |
397 | ||
398 | /** | |
399 | * voltdm_for_each - call function on each registered voltagedomain | |
400 | * @fn: callback function * | |
401 | * | |
402 | * Call the supplied function @fn for each registered voltagedomain. | |
403 | * The callback function @fn can return anything but 0 to bail out | |
404 | * early from the iterator. Returns the last return value of the | |
405 | * callback function, which should be 0 for success or anything else | |
406 | * to indicate failure; or -EINVAL if the function pointer is null. | |
407 | */ | |
408 | int voltdm_for_each(int (*fn)(struct voltagedomain *voltdm, void *user), | |
409 | void *user) | |
410 | { | |
411 | struct voltagedomain *temp_voltdm; | |
412 | int ret = 0; | |
413 | ||
414 | if (!fn) | |
415 | return -EINVAL; | |
416 | ||
417 | list_for_each_entry(temp_voltdm, &voltdm_list, node) { | |
418 | ret = (*fn)(temp_voltdm, user); | |
419 | if (ret) | |
420 | break; | |
421 | } | |
422 | ||
423 | return ret; | |
424 | } | |
425 | ||
81a60482 KH |
426 | static int _voltdm_register(struct voltagedomain *voltdm) |
427 | { | |
428 | if (!voltdm || !voltdm->name) | |
429 | return -EINVAL; | |
430 | ||
e69c22b1 | 431 | INIT_LIST_HEAD(&voltdm->pwrdm_list); |
81a60482 KH |
432 | list_add(&voltdm->node, &voltdm_list); |
433 | ||
434 | pr_debug("voltagedomain: registered %s\n", voltdm->name); | |
435 | ||
2f34ce81 TG |
436 | return 0; |
437 | } | |
81a60482 KH |
438 | |
439 | /** | |
440 | * voltdm_lookup - look up a voltagedomain by name, return a pointer | |
441 | * @name: name of voltagedomain | |
442 | * | |
443 | * Find a registered voltagedomain by its name @name. Returns a pointer | |
444 | * to the struct voltagedomain if found, or NULL otherwise. | |
445 | */ | |
446 | struct voltagedomain *voltdm_lookup(const char *name) | |
447 | { | |
448 | struct voltagedomain *voltdm ; | |
449 | ||
450 | if (!name) | |
451 | return NULL; | |
452 | ||
453 | voltdm = _voltdm_lookup(name); | |
454 | ||
455 | return voltdm; | |
456 | } | |
457 | ||
458 | /** | |
459 | * voltdm_init - set up the voltagedomain layer | |
460 | * @voltdm_list: array of struct voltagedomain pointers to register | |
461 | * | |
462 | * Loop through the array of voltagedomains @voltdm_list, registering all | |
463 | * that are available on the current CPU. If voltdm_list is supplied | |
464 | * and not null, all of the referenced voltagedomains will be | |
465 | * registered. No return value. | |
466 | */ | |
467 | void voltdm_init(struct voltagedomain **voltdms) | |
468 | { | |
469 | struct voltagedomain **v; | |
470 | ||
471 | if (voltdms) { | |
472 | for (v = voltdms; *v; v++) | |
473 | _voltdm_register(*v); | |
474 | } | |
475 | } |