Commit 866c74bf7df1c53b283c45aee1a6105361a9bb47
Committed by
Afzal Mohammed
1 parent
8d9459b2cf
Exists in
master
OMAP3+: PM: DVFS: introduce helper functions for DVFS
This patch introduces helper functions for Device Voltage and Frequency Scaling (DVFS). Data structures added: 1. omap_dev_user_list: This structure maintains list of frequency requests per device basis. When a device needs to be scaled to a particular frequency, This list is searched to find the maximum request for a given device. If noone has placed any request, device frequency is obtained from device opp table. 2. omap_vdd_dev_list: This strcucture stores device list per vdd basis. Whenever a device is registered with a vdd, it is added to this list. 3. omap_vdd_user_list: User list of devices associated with each voltage domain instance. The user list is implemented using plist structure with priority node populated with the voltage values. 4. omap_vdd_dvfs_info: This structure is used to abstract DVFS related information per VDD basis. It holds pointer to corresponding vdd's voltagedomain instance and pointer to user list. Following helper functions have been added to operate on above data structures: 1. omap_dvfs_add_vdd_user - function to add a user into omap_vdd_user_list 2. omap_vdd_user_list - function to remove a user from omap_vdd_user_list 3. omap_dvfs_register_device - function to register a device with vdd 4. omap_dvfs_add_freq_request - function to add a frequency request into omap_dev_user_list 5. omap_dvfs_remove_freq_request - function to remove a frequency request from omap_dev_user_list 6. omap_dvfs_find_voltage - function to find the opp corresponding to given voltage 7. omap_device_scale - this is the function that is to be used for scaling a specific domain. This is exported for drivers such as syslink, dspbridge to use it while being a module. DVFS layer is initialized and basic data structures are allocated and initialized as part of this. This patch is based on Vishwa and Thara's previous DVFS implementation, but with major rework. note: generates "warning: 'omap_dvfs_remove_vdd_user' defined but not used" this function will be used in future patches [nm@ti.com: misc cleanups including use voltage files from mach-omap2 dir] [axelhaslam@ti.com: fixed issue with ondemand - required mutex unlock] [toddpoynor@google.com: Add spinlocking of plists, GFP_ATOMIC] Signed-off-by: Nishanth Menon <nm@ti.com> Signed-off-by: Axel Haslam <axelhaslam@ti.com> Signed-off-by: Todd Poynor <toddpoynor@google.com> Signed-off-by: Vishwanath BS <vishwanath.bs@ti.com> Cc: Thara Gopinath <thara@ti.com> [vaibhav.bedia@ti.com: Pull in for AM33xx] Signed-off-by: Vaibhav Bedia <vaibhav.bedia@ti.com> [afzal@ti.com: fix warning] Signed-off-by: Afzal Mohammed <afzal@ti.com>
Showing 3 changed files with 1018 additions and 2 deletions Side-by-side Diff
arch/arm/mach-omap2/Makefile
... | ... | @@ -80,7 +80,7 @@ |
80 | 80 | # PRCM |
81 | 81 | obj-$(CONFIG_ARCH_OMAP2) += prcm.o cm2xxx_3xxx.o prm2xxx_3xxx.o |
82 | 82 | obj-$(CONFIG_ARCH_OMAP3) += prcm.o cm2xxx_3xxx.o prm2xxx_3xxx.o \ |
83 | - vc3xxx_data.o vp3xxx_data.o | |
83 | + vc3xxx_data.o vp3xxx_data.o dvfs.o | |
84 | 84 | obj-$(CONFIG_SOC_OMAPAM33XX) += cminst33xx.o prminst33xx.o cm33xx.o |
85 | 85 | |
86 | 86 | # XXX The presence of cm2xxx_3xxx.o on the line below is temporary and |
... | ... | @@ -89,7 +89,7 @@ |
89 | 89 | obj-$(CONFIG_ARCH_OMAP4) += prcm.o cm2xxx_3xxx.o cminst44xx.o \ |
90 | 90 | cm44xx.o prcm_mpu44xx.o \ |
91 | 91 | prminst44xx.o vc44xx_data.o \ |
92 | - vp44xx_data.o | |
92 | + vp44xx_data.o dvfs.o | |
93 | 93 | |
94 | 94 | # OMAP voltage domains |
95 | 95 | voltagedomain-common := voltage.o vc.o vp.o |
arch/arm/mach-omap2/dvfs.c
1 | +/* | |
2 | + * OMAP3/OMAP4 DVFS Management Routines | |
3 | + * | |
4 | + * Author: Vishwanath BS <vishwanath.bs@ti.com> | |
5 | + * | |
6 | + * Copyright (C) 2011 Texas Instruments, Inc. | |
7 | + * Vishwanath BS <vishwanath.bs@ti.com> | |
8 | + * | |
9 | + * This program is free software; you can redistribute it and/or modify | |
10 | + * it under the terms of the GNU General Public License version 2 as | |
11 | + * published by the Free Software Foundation. | |
12 | + */ | |
13 | + | |
14 | +#include <linux/err.h> | |
15 | +#include <linux/spinlock.h> | |
16 | +#include <linux/plist.h> | |
17 | +#include <linux/slab.h> | |
18 | +#include <linux/opp.h> | |
19 | +#include <linux/clk.h> | |
20 | +#include <plat/common.h> | |
21 | +#include <plat/omap_device.h> | |
22 | +#include <plat/omap_hwmod.h> | |
23 | +#include <plat/clock.h> | |
24 | +#include "dvfs.h" | |
25 | +#include "smartreflex.h" | |
26 | +#include "powerdomain.h" | |
27 | + | |
28 | +/** | |
29 | + * DOC: Introduction | |
30 | + * ================= | |
31 | + * DVFS is a technique that uses the optimal operating frequency and voltage to | |
32 | + * allow a task to be performed in the required amount of time. | |
33 | + * OMAP processors have voltage domains whose voltage can be scaled to | |
34 | + * various levels depending on which the operating frequencies of certain | |
35 | + * devices belonging to the domain will also need to be scaled. This voltage | |
36 | + * frequency tuple is known as Operating Performance Point (OPP). A device | |
37 | + * can have multiple OPP's. Also a voltage domain could be shared between | |
38 | + * multiple devices. Also there could be dependencies between various | |
39 | + * voltage domains for maintaining system performance like VDD<X> | |
40 | + * should be at voltage v1 when VDD<Y> is at voltage v2. | |
41 | + * | |
42 | + * The design of this framework takes into account all the above mentioned | |
43 | + * points. To summarize the basic design of DVFS framework:- | |
44 | + * | |
45 | + * 1. Have device opp tables for each device whose operating frequency can be | |
46 | + * scaled. This is easy now due to the existance of hwmod layer which | |
47 | + * allow storing of device specific info. The device opp tables contain | |
48 | + * the opp pairs (frequency voltage tuples), the voltage domain pointer | |
49 | + * to which the device belongs to, the device specific set_rate and | |
50 | + * get_rate API's which will do the actual scaling of the device frequency | |
51 | + * and retrieve the current device frequency. | |
52 | + * 2. Introduce use counting on a per VDD basis. This is to take care multiple | |
53 | + * requests to scale a VDD. The VDD will be scaled to the maximum of the | |
54 | + * voltages requested. | |
55 | + * 3. Keep track of all scalable devices belonging to a particular voltage | |
56 | + * domain the voltage layer. | |
57 | + * 4. Keep track of frequency requests for each of the device. This will enable | |
58 | + * to scale individual devices to different frequency (even w/o scaling | |
59 | + * voltage aka frequency throttling) | |
60 | + * 5. Generic dvfs API that can be called by anybody to scale a device opp. | |
61 | + * This API takes the device pointer and frequency to which the device | |
62 | + * needs to be scaled to. This API then internally finds out the voltage | |
63 | + * domain to which the device belongs to and the voltage to which the voltage | |
64 | + * domain needs to be put to for the device to be scaled to the new frequency | |
65 | + * from he device opp table. Then this API will add requested frequency into | |
66 | + * the corresponding target device frequency list and add voltage request to | |
67 | + * the corresponding vdd. Subsequently it calls voltage scale function which | |
68 | + * will find out the highest requested voltage for the given vdd and scales | |
69 | + * the voltage to the required one. It also runs through the list of all | |
70 | + * scalable devices belonging to this voltage domain and scale them to the | |
71 | + * appropriate frequencies using the set_rate pointer in the device opp | |
72 | + * tables. | |
73 | + * 6. Handle inter VDD dependecies. | |
74 | + * | |
75 | + * | |
76 | + * DOC: The Core DVFS data structure: | |
77 | + * ================================== | |
78 | + * Structure Name Example Tree | |
79 | + * --------- | |
80 | + * /|\ +-------------------+ +-------------------+ | |
81 | + * | |User2 (dev2, freq2)+---\ |User4 (dev4, freq4)+---\ | |
82 | + * | +-------------------+ | +-------------------+ | | |
83 | + * (struct omap_dev_user_list) | | | |
84 | + * | +-------------------+ | +-------------------+ | | |
85 | + * | |User1 (dev1, freq1)+---| |User3 (dev3, freq3)+---| | |
86 | + * \|/ +-------------------+ | +-------------------+ | | |
87 | + * --------- | | | |
88 | + * /|\ +------------+------+ +---------------+--+ | |
89 | + * | | DEV1 (dev, | | DEV2 (dev) | | |
90 | + * (struct omap_vdd_dev_list)|omap_dev_user_list)| |omap_dev_user_list| | |
91 | + * | +------------+------+ +--+---------------+ | |
92 | + * \|/ /|\ /-----+-------------+------> others.. | |
93 | + * --------- Frequency | | |
94 | + * /|\ +--+------------------+ | |
95 | + * | | VDD_n | | |
96 | + * | | (omap_vdd_dev_list, | | |
97 | + * (struct omap_vdd_dvfs_info)** | omap_vdd_user_list) | | |
98 | + * | +--+------------------+ | |
99 | + * | | (ROOT NODE: omap_dvfs_info_list) | |
100 | + * \|/ | | |
101 | + * --------- Voltage \---+-------------+----------> others.. | |
102 | + * /|\ \|/ +-------+----+ +-----+--------+ | |
103 | + * | | vdd_user2 | | vdd_user3 | | |
104 | + * (struct omap_vdd_user_list) | (dev, volt)| | (dev, volt) | | |
105 | + * \|/ +------------+ +--------------+ | |
106 | + * --------- | |
107 | + * Key: ** -> Root of the tree. | |
108 | + * NOTE: we use the priority to store the voltage/frequency | |
109 | + * | |
110 | + * For voltage dependency description, see: struct dependency: | |
111 | + * voltagedomain -> (description of the voltagedomain) | |
112 | + * omap_vdd_info -> (vdd information) | |
113 | + * omap_vdd_dep_info[]-> (stores array of depedency info) | |
114 | + * omap_vdd_dep_volt[] -> (stores array of maps) | |
115 | + * (main_volt -> dep_volt) (a singular map) | |
116 | + */ | |
117 | + | |
118 | +/* Macros to give idea about scaling directions */ | |
119 | +#define DVFS_VOLT_SCALE_DOWN 0 | |
120 | +#define DVFS_VOLT_SCALE_NONE 1 | |
121 | +#define DVFS_VOLT_SCALE_UP 2 | |
122 | + | |
123 | +/** | |
124 | + * struct omap_dev_user_list - Structure maitain userlist per devide | |
125 | + * @dev: The device requesting for a particular frequency | |
126 | + * @node: The list head entry | |
127 | + * | |
128 | + * Using this structure, user list (requesting dev * and frequency) for | |
129 | + * each device is maintained. This is how we can have different devices | |
130 | + * at different frequencies (to support frequency locking and throttling). | |
131 | + * Even if one of the devices in a given vdd has locked it's frequency, | |
132 | + * other's can still scale their frequency using this list. | |
133 | + * If no one has placed a frequency request for a device, then device is | |
134 | + * set to the frequency from it's opp table. | |
135 | + */ | |
136 | +struct omap_dev_user_list { | |
137 | + struct device *dev; | |
138 | + struct plist_node node; | |
139 | +}; | |
140 | + | |
141 | +/** | |
142 | + * struct omap_vdd_dev_list - Device list per vdd | |
143 | + * @dev: The device belonging to a particular vdd | |
144 | + * @node: The list head entry | |
145 | + * @freq_user_list: The list of users for vdd device | |
146 | + * @clk: frequency control clock for this dev | |
147 | + * @user_lock: The lock for plist manipulation | |
148 | + */ | |
149 | +struct omap_vdd_dev_list { | |
150 | + struct device *dev; | |
151 | + struct list_head node; | |
152 | + struct plist_head freq_user_list; | |
153 | + struct clk *clk; | |
154 | + spinlock_t user_lock; /* spinlock for plist */ | |
155 | +}; | |
156 | + | |
157 | +/** | |
158 | + * struct omap_vdd_user_list - The per vdd user list | |
159 | + * @dev: The device asking for the vdd to be set at a particular | |
160 | + * voltage | |
161 | + * @node: The list head entry | |
162 | + */ | |
163 | +struct omap_vdd_user_list { | |
164 | + struct device *dev; | |
165 | + struct plist_node node; | |
166 | +}; | |
167 | + | |
168 | +/** | |
169 | + * struct omap_vdd_dvfs_info - The per vdd dvfs info | |
170 | + * @node: list node for vdd_dvfs_info list | |
171 | + * @user_lock: spinlock for plist operations | |
172 | + * @vdd_user_list: The vdd user list | |
173 | + * @voltdm: Voltage domains for which dvfs info stored | |
174 | + * @dev_list: Device list maintained per domain | |
175 | + * | |
176 | + * This is a fundamental structure used to store all the required | |
177 | + * DVFS related information for a vdd. | |
178 | + */ | |
179 | +struct omap_vdd_dvfs_info { | |
180 | + struct list_head node; | |
181 | + | |
182 | + spinlock_t user_lock; /* spin lock */ | |
183 | + struct plist_head vdd_user_list; | |
184 | + struct voltagedomain *voltdm; | |
185 | + struct list_head dev_list; | |
186 | +}; | |
187 | + | |
188 | +static LIST_HEAD(omap_dvfs_info_list); | |
189 | +static DEFINE_MUTEX(omap_dvfs_lock); | |
190 | + | |
191 | +/* Dvfs scale helper function */ | |
192 | +static int _dvfs_scale(struct device *req_dev, struct device *target_dev, | |
193 | + struct omap_vdd_dvfs_info *tdvfs_info); | |
194 | + | |
195 | +/* Few search functions to traverse and find pointers of interest */ | |
196 | + | |
197 | +/** | |
198 | + * _dvfs_info_to_dev() - Locate the parent device associated to dvfs_info | |
199 | + * @dvfs_info: dvfs_info to search for | |
200 | + * | |
201 | + * Returns NULL on failure. | |
202 | + */ | |
203 | +static struct device *_dvfs_info_to_dev(struct omap_vdd_dvfs_info *dvfs_info) | |
204 | +{ | |
205 | + struct omap_vdd_dev_list *tmp_dev; | |
206 | + if (IS_ERR_OR_NULL(dvfs_info)) | |
207 | + return NULL; | |
208 | + if (list_empty(&dvfs_info->dev_list)) | |
209 | + return NULL; | |
210 | + tmp_dev = list_first_entry(&dvfs_info->dev_list, | |
211 | + struct omap_vdd_dev_list, node); | |
212 | + return tmp_dev->dev; | |
213 | +} | |
214 | + | |
215 | +/** | |
216 | + * _dev_to_dvfs_info() - Locate the dvfs_info for a device | |
217 | + * @dev: dev to search for | |
218 | + * | |
219 | + * Returns NULL on failure. | |
220 | + */ | |
221 | +static struct omap_vdd_dvfs_info *_dev_to_dvfs_info(struct device *dev) | |
222 | +{ | |
223 | + struct omap_vdd_dvfs_info *dvfs_info; | |
224 | + struct omap_vdd_dev_list *temp_dev; | |
225 | + | |
226 | + if (IS_ERR_OR_NULL(dev)) | |
227 | + return NULL; | |
228 | + | |
229 | + list_for_each_entry(dvfs_info, &omap_dvfs_info_list, node) { | |
230 | + list_for_each_entry(temp_dev, &dvfs_info->dev_list, node) { | |
231 | + if (temp_dev->dev == dev) | |
232 | + return dvfs_info; | |
233 | + } | |
234 | + } | |
235 | + | |
236 | + return NULL; | |
237 | +} | |
238 | + | |
239 | +/** | |
240 | + * _voltdm_to_dvfs_info() - Locate a dvfs_info given a voltdm pointer | |
241 | + * @voltdm: voltdm to search for | |
242 | + * | |
243 | + * Returns NULL on failure. | |
244 | + */ | |
245 | +static | |
246 | +struct omap_vdd_dvfs_info *_voltdm_to_dvfs_info(struct voltagedomain *voltdm) | |
247 | +{ | |
248 | + struct omap_vdd_dvfs_info *dvfs_info; | |
249 | + | |
250 | + if (IS_ERR_OR_NULL(voltdm)) | |
251 | + return NULL; | |
252 | + | |
253 | + list_for_each_entry(dvfs_info, &omap_dvfs_info_list, node) { | |
254 | + if (dvfs_info->voltdm == voltdm) | |
255 | + return dvfs_info; | |
256 | + } | |
257 | + | |
258 | + return NULL; | |
259 | +} | |
260 | + | |
261 | +/** | |
262 | + * _volt_to_opp() - Find OPP corresponding to a given voltage | |
263 | + * @dev: device pointer associated with the OPP list | |
264 | + * @volt: voltage to search for in uV | |
265 | + * | |
266 | + * Searches for exact match in the OPP list and returns handle to the matching | |
267 | + * OPP if found, else returns ERR_PTR in case of error and should be handled | |
268 | + * using IS_ERR. If there are multiple opps with same voltage, it will return | |
269 | + * the first available entry. Return pointer should be checked against IS_ERR. | |
270 | + * | |
271 | + * NOTE: since this uses OPP functions, use under rcu_lock. This function also | |
272 | + * assumes that the cpufreq table and OPP table are in sync - any modifications | |
273 | + * to either should be synchronized. | |
274 | + */ | |
275 | +static struct opp *_volt_to_opp(struct device *dev, unsigned long volt) | |
276 | +{ | |
277 | + struct opp *opp = ERR_PTR(-ENODEV); | |
278 | + unsigned long f = 0; | |
279 | + | |
280 | + do { | |
281 | + opp = opp_find_freq_ceil(dev, &f); | |
282 | + if (IS_ERR(opp)) | |
283 | + break; | |
284 | + if (opp_get_voltage(opp) >= volt) | |
285 | + break; | |
286 | + f++; | |
287 | + } while (1); | |
288 | + | |
289 | + return opp; | |
290 | +} | |
291 | + | |
292 | +/* rest of the helper functions */ | |
293 | +/** | |
294 | + * _add_vdd_user() - Add a voltage request | |
295 | + * @dvfs_info: omap_vdd_dvfs_info pointer for the required vdd | |
296 | + * @dev: device making the request | |
297 | + * @volt: requested voltage in uV | |
298 | + * | |
299 | + * Adds the given device's voltage request into corresponding | |
300 | + * vdd's omap_vdd_dvfs_info user list (plist). This list is used | |
301 | + * to find the maximum voltage request for a given vdd. | |
302 | + * | |
303 | + * Returns 0 on success. | |
304 | + */ | |
305 | +static int _add_vdd_user(struct omap_vdd_dvfs_info *dvfs_info, | |
306 | + struct device *dev, unsigned long volt) | |
307 | +{ | |
308 | + struct omap_vdd_user_list *user = NULL, *temp_user; | |
309 | + | |
310 | + if (!dvfs_info || IS_ERR(dvfs_info)) { | |
311 | + dev_warn(dev, "%s: VDD specified does not exist!\n", __func__); | |
312 | + return -EINVAL; | |
313 | + } | |
314 | + | |
315 | + spin_lock(&dvfs_info->user_lock); | |
316 | + plist_for_each_entry(temp_user, &dvfs_info->vdd_user_list, node) { | |
317 | + if (temp_user->dev == dev) { | |
318 | + user = temp_user; | |
319 | + break; | |
320 | + } | |
321 | + } | |
322 | + | |
323 | + if (!user) { | |
324 | + user = kzalloc(sizeof(struct omap_vdd_user_list), GFP_ATOMIC); | |
325 | + if (!user) { | |
326 | + dev_err(dev, | |
327 | + "%s: Unable to creat a new user for vdd_%s\n", | |
328 | + __func__, dvfs_info->voltdm->name); | |
329 | + spin_unlock(&dvfs_info->user_lock); | |
330 | + return -ENOMEM; | |
331 | + } | |
332 | + user->dev = dev; | |
333 | + } else { | |
334 | + plist_del(&user->node, &dvfs_info->vdd_user_list); | |
335 | + } | |
336 | + | |
337 | + plist_node_init(&user->node, volt); | |
338 | + plist_add(&user->node, &dvfs_info->vdd_user_list); | |
339 | + | |
340 | + spin_unlock(&dvfs_info->user_lock); | |
341 | + return 0; | |
342 | +} | |
343 | + | |
344 | +/** | |
345 | + * _remove_vdd_user() - Remove a voltage request | |
346 | + * @dvfs_info: omap_vdd_dvfs_info pointer for the required vdd | |
347 | + * @dev: device making the request | |
348 | + * | |
349 | + * Removes the given device's voltage request from corresponding | |
350 | + * vdd's omap_vdd_dvfs_info user list (plist). | |
351 | + * | |
352 | + * Returns 0 on success. | |
353 | + */ | |
354 | +static int _remove_vdd_user(struct omap_vdd_dvfs_info *dvfs_info, | |
355 | + struct device *dev) | |
356 | +{ | |
357 | + struct omap_vdd_user_list *user = NULL, *temp_user; | |
358 | + int ret = 0; | |
359 | + | |
360 | + if (!dvfs_info || IS_ERR(dvfs_info)) { | |
361 | + dev_err(dev, "%s: VDD specified does not exist!\n", __func__); | |
362 | + return -EINVAL; | |
363 | + } | |
364 | + | |
365 | + spin_lock(&dvfs_info->user_lock); | |
366 | + plist_for_each_entry(temp_user, &dvfs_info->vdd_user_list, node) { | |
367 | + if (temp_user->dev == dev) { | |
368 | + user = temp_user; | |
369 | + break; | |
370 | + } | |
371 | + } | |
372 | + | |
373 | + if (user) | |
374 | + plist_del(&user->node, &dvfs_info->vdd_user_list); | |
375 | + else { | |
376 | + dev_err(dev, "%s: Unable to find the user for vdd_%s\n", | |
377 | + __func__, dvfs_info->voltdm->name); | |
378 | + ret = -ENOENT; | |
379 | + } | |
380 | + | |
381 | + spin_unlock(&dvfs_info->user_lock); | |
382 | + kfree(user); | |
383 | + | |
384 | + return ret; | |
385 | +} | |
386 | + | |
387 | +/** | |
388 | + * _add_freq_request() - Add a requested device frequency | |
389 | + * @dvfs_info: omap_vdd_dvfs_info pointer for the required vdd | |
390 | + * @req_dev: device making the request | |
391 | + * @target_dev: target device for which frequency request is being made | |
392 | + * @freq: target device frequency | |
393 | + * | |
394 | + * This adds a requested frequency into target device's frequency list. | |
395 | + * | |
396 | + * Returns 0 on success. | |
397 | + */ | |
398 | +static int _add_freq_request(struct omap_vdd_dvfs_info *dvfs_info, | |
399 | + struct device *req_dev, struct device *target_dev, unsigned long freq) | |
400 | +{ | |
401 | + struct omap_dev_user_list *dev_user = NULL, *tmp_user; | |
402 | + struct omap_vdd_dev_list *temp_dev; | |
403 | + | |
404 | + if (!dvfs_info || IS_ERR(dvfs_info)) { | |
405 | + dev_warn(target_dev, "%s: VDD specified does not exist!\n", | |
406 | + __func__); | |
407 | + return -EINVAL; | |
408 | + } | |
409 | + | |
410 | + list_for_each_entry(temp_dev, &dvfs_info->dev_list, node) { | |
411 | + if (temp_dev->dev == target_dev) | |
412 | + break; | |
413 | + } | |
414 | + | |
415 | + if (temp_dev->dev != target_dev) { | |
416 | + dev_warn(target_dev, "%s: target_dev does not exist!\n", | |
417 | + __func__); | |
418 | + return -EINVAL; | |
419 | + } | |
420 | + | |
421 | + spin_lock(&temp_dev->user_lock); | |
422 | + plist_for_each_entry(tmp_user, &temp_dev->freq_user_list, node) { | |
423 | + if (tmp_user->dev == req_dev) { | |
424 | + dev_user = tmp_user; | |
425 | + break; | |
426 | + } | |
427 | + } | |
428 | + | |
429 | + if (!dev_user) { | |
430 | + dev_user = kzalloc(sizeof(struct omap_dev_user_list), | |
431 | + GFP_ATOMIC); | |
432 | + if (!dev_user) { | |
433 | + dev_err(target_dev, | |
434 | + "%s: Unable to creat a new user for vdd_%s\n", | |
435 | + __func__, dvfs_info->voltdm->name); | |
436 | + spin_unlock(&temp_dev->user_lock); | |
437 | + return -ENOMEM; | |
438 | + } | |
439 | + dev_user->dev = req_dev; | |
440 | + } else { | |
441 | + plist_del(&dev_user->node, &temp_dev->freq_user_list); | |
442 | + } | |
443 | + | |
444 | + plist_node_init(&dev_user->node, freq); | |
445 | + plist_add(&dev_user->node, &temp_dev->freq_user_list); | |
446 | + spin_unlock(&temp_dev->user_lock); | |
447 | + return 0; | |
448 | +} | |
449 | + | |
450 | +/** | |
451 | + * _remove_freq_request() - Remove the requested device frequency | |
452 | + * | |
453 | + * @dvfs_info: omap_vdd_dvfs_info pointer for the required vdd | |
454 | + * @req_dev: device removing the request | |
455 | + * @target_dev: target device from which frequency request is being removed | |
456 | + * | |
457 | + * This removes a requested frequency from target device's frequency list. | |
458 | + * | |
459 | + * Returns 0 on success. | |
460 | + */ | |
461 | +static int _remove_freq_request(struct omap_vdd_dvfs_info *dvfs_info, | |
462 | + struct device *req_dev, struct device *target_dev) | |
463 | +{ | |
464 | + struct omap_dev_user_list *dev_user = NULL, *tmp_user; | |
465 | + int ret = 0; | |
466 | + struct omap_vdd_dev_list *temp_dev; | |
467 | + | |
468 | + if (!dvfs_info || IS_ERR(dvfs_info)) { | |
469 | + dev_warn(target_dev, "%s: VDD specified does not exist!\n", | |
470 | + __func__); | |
471 | + return -EINVAL; | |
472 | + } | |
473 | + | |
474 | + | |
475 | + list_for_each_entry(temp_dev, &dvfs_info->dev_list, node) { | |
476 | + if (temp_dev->dev == target_dev) | |
477 | + break; | |
478 | + } | |
479 | + | |
480 | + if (temp_dev->dev != target_dev) { | |
481 | + dev_warn(target_dev, "%s: target_dev does not exist!\n", | |
482 | + __func__); | |
483 | + return -EINVAL; | |
484 | + } | |
485 | + | |
486 | + spin_lock(&temp_dev->user_lock); | |
487 | + plist_for_each_entry(tmp_user, &temp_dev->freq_user_list, node) { | |
488 | + if (tmp_user->dev == req_dev) { | |
489 | + dev_user = tmp_user; | |
490 | + break; | |
491 | + } | |
492 | + } | |
493 | + | |
494 | + if (dev_user) { | |
495 | + plist_del(&dev_user->node, &temp_dev->freq_user_list); | |
496 | + } else { | |
497 | + dev_err(target_dev, | |
498 | + "%s: Unable to remove the user for vdd_%s\n", | |
499 | + __func__, dvfs_info->voltdm->name); | |
500 | + ret = -EINVAL; | |
501 | + } | |
502 | + | |
503 | + spin_unlock(&temp_dev->user_lock); | |
504 | + kfree(dev_user); | |
505 | + | |
506 | + return ret; | |
507 | +} | |
508 | + | |
509 | +/** | |
510 | + * _dep_scan_table() - Scan a dependency table and mark for scaling | |
511 | + * @dev: device requesting the dependency scan (req_dev) | |
512 | + * @dep_info: dependency information (contains the table) | |
513 | + * @main_volt: voltage dependency to search for | |
514 | + * | |
515 | + * This runs down the table provided to find the match for main_volt | |
516 | + * provided and sets up a scale request for the dependent domain | |
517 | + * for the dependent voltage. | |
518 | + * | |
519 | + * Returns 0 if all went well. | |
520 | + */ | |
521 | +static int _dep_scan_table(struct device *dev, | |
522 | + struct omap_vdd_dep_info *dep_info, unsigned long main_volt) | |
523 | +{ | |
524 | + struct omap_vdd_dep_volt *dep_table = dep_info->dep_table; | |
525 | + int i; | |
526 | + unsigned long dep_volt = 0; | |
527 | + | |
528 | + if (!dep_table) { | |
529 | + dev_err(dev, "%s: deptable not present for vdd%s\n", | |
530 | + __func__, dep_info->name); | |
531 | + return -EINVAL; | |
532 | + } | |
533 | + | |
534 | + /* Now scan through the the dep table for a match */ | |
535 | + for (i = 0; i < dep_info->nr_dep_entries; i++) { | |
536 | + if (dep_table[i].main_vdd_volt == main_volt) { | |
537 | + dep_volt = dep_table[i].dep_vdd_volt; | |
538 | + break; | |
539 | + } | |
540 | + } | |
541 | + if (!dep_volt) { | |
542 | + dev_warn(dev, "%s: %ld volt map missing in vdd_%s\n", | |
543 | + __func__, main_volt, dep_info->name); | |
544 | + return -EINVAL; | |
545 | + } | |
546 | + | |
547 | + /* populate voltdm if it is not present */ | |
548 | + if (!dep_info->_dep_voltdm) { | |
549 | + dep_info->_dep_voltdm = voltdm_lookup(dep_info->name); | |
550 | + if (!dep_info->_dep_voltdm) { | |
551 | + dev_warn(dev, "%s: unable to get vdm%s\n", | |
552 | + __func__, dep_info->name); | |
553 | + return -ENODEV; | |
554 | + } | |
555 | + } | |
556 | + | |
557 | + /* See if dep_volt is possible for the vdd*/ | |
558 | + i = _add_vdd_user(_voltdm_to_dvfs_info(dep_info->_dep_voltdm), | |
559 | + dev, dep_volt); | |
560 | + if (i) | |
561 | + dev_err(dev, "%s: Failed to add dep to domain %s volt=%ld\n", | |
562 | + __func__, dep_info->name, dep_volt); | |
563 | + return i; | |
564 | +} | |
565 | + | |
566 | +/** | |
567 | + * _dep_scan_domains() - Scan dependency domains for a device | |
568 | + * @dev: device requesting the scan | |
569 | + * @vdd: vdd_info corresponding to the device | |
570 | + * @main_volt: voltage to scan for | |
571 | + * | |
572 | + * Since each domain *may* have multiple dependent domains, we scan | |
573 | + * through each of the dependent domains and invoke _dep_scan_table to | |
574 | + * scan each table for dependent domain for dependency scaling. | |
575 | + * | |
576 | + * This assumes that the dependent domain information is NULL entry terminated. | |
577 | + * Returns 0 if all went well. | |
578 | + */ | |
579 | +static int _dep_scan_domains(struct device *dev, | |
580 | + struct omap_vdd_info *vdd, unsigned long main_volt) | |
581 | +{ | |
582 | + struct omap_vdd_dep_info *dep_info = vdd->dep_vdd_info; | |
583 | + int ret = 0, r; | |
584 | + | |
585 | + if (!dep_info) { | |
586 | + dev_dbg(dev, "%s: No dependent VDD\n", __func__); | |
587 | + return 0; | |
588 | + } | |
589 | + | |
590 | + /* First scan through the mydomain->dep_domain list */ | |
591 | + while (dep_info->nr_dep_entries) { | |
592 | + r = _dep_scan_table(dev, dep_info, main_volt); | |
593 | + /* Store last failed value */ | |
594 | + ret = (r) ? r : ret; | |
595 | + dep_info++; | |
596 | + } | |
597 | + | |
598 | + return ret; | |
599 | +} | |
600 | + | |
601 | +/** | |
602 | + * _dep_scale_domains() - Cause a scale of all dependent domains | |
603 | + * @req_dev: device requesting the scale | |
604 | + * @req_vdd: vdd_info corresponding to the requesting device. | |
605 | + * | |
606 | + * This walks through every dependent domain and triggers a scale | |
607 | + * It is assumed that the corresponding scale handling for the | |
608 | + * domain translates this to freq and voltage scale operations as | |
609 | + * needed. | |
610 | + * | |
611 | + * Note: This is uses _dvfs_scale and one should be careful not to | |
612 | + * create a circular depedency (e.g. vdd_mpu->vdd_core->vdd->mpu) | |
613 | + * which can create deadlocks. No protection is provided to prevent | |
614 | + * this condition and a tree organization is assumed. | |
615 | + * | |
616 | + * Returns 0 if all went fine. | |
617 | + */ | |
618 | +static int _dep_scale_domains(struct device *req_dev, | |
619 | + struct omap_vdd_info *req_vdd) | |
620 | +{ | |
621 | + struct omap_vdd_dep_info *dep_info = req_vdd->dep_vdd_info; | |
622 | + int ret = 0, r; | |
623 | + | |
624 | + if (!dep_info) { | |
625 | + dev_dbg(req_dev, "%s: No dependent VDD\n", __func__); | |
626 | + return 0; | |
627 | + } | |
628 | + | |
629 | + /* First scan through the mydomain->dep_domain list */ | |
630 | + while (dep_info->nr_dep_entries) { | |
631 | + struct voltagedomain *tvoltdm = dep_info->_dep_voltdm; | |
632 | + | |
633 | + r = 0; | |
634 | + /* Scale it only if I have a voltdm mapped up for the dep */ | |
635 | + if (tvoltdm) { | |
636 | + struct omap_vdd_dvfs_info *tdvfs_info; | |
637 | + struct device *target_dev; | |
638 | + tdvfs_info = _voltdm_to_dvfs_info(tvoltdm); | |
639 | + if (!tdvfs_info) { | |
640 | + dev_warn(req_dev, "%s: no dvfs_info\n", | |
641 | + __func__); | |
642 | + goto next; | |
643 | + } | |
644 | + target_dev = _dvfs_info_to_dev(tdvfs_info); | |
645 | + if (!target_dev) { | |
646 | + dev_warn(req_dev, "%s: no target_dev\n", | |
647 | + __func__); | |
648 | + goto next; | |
649 | + } | |
650 | + r = _dvfs_scale(req_dev, target_dev, tdvfs_info); | |
651 | +next: | |
652 | + if (r) | |
653 | + dev_err(req_dev, "%s: dvfs_scale to %s =%d\n", | |
654 | + __func__, dev_name(target_dev), r); | |
655 | + } | |
656 | + /* Store last failed value */ | |
657 | + ret = (r) ? r : ret; | |
658 | + dep_info++; | |
659 | + } | |
660 | + | |
661 | + return ret; | |
662 | +} | |
663 | + | |
664 | +/** | |
665 | + * _dvfs_scale() : Scale the devices associated with a voltage domain | |
666 | + * @req_dev: Device requesting the scale | |
667 | + * @target_dev: Device requesting to be scaled | |
668 | + * @tdvfs_info: omap_vdd_dvfs_info pointer for the target domain | |
669 | + * | |
670 | + * This runs through the list of devices associated with the | |
671 | + * voltage domain and scales the device rates to the one requested | |
672 | + * by the user or those corresponding to the new voltage of the | |
673 | + * voltage domain. Target voltage is the highest voltage in the vdd_user_list. | |
674 | + * | |
675 | + * Returns 0 on success else the error value. | |
676 | + */ | |
677 | +static int _dvfs_scale(struct device *req_dev, struct device *target_dev, | |
678 | + struct omap_vdd_dvfs_info *tdvfs_info) | |
679 | +{ | |
680 | + unsigned long curr_volt, new_volt; | |
681 | + int volt_scale_dir = DVFS_VOLT_SCALE_DOWN; | |
682 | + struct omap_vdd_dev_list *temp_dev; | |
683 | + struct plist_node *node; | |
684 | + int ret = 0; | |
685 | + struct voltagedomain *voltdm; | |
686 | + struct omap_vdd_info *vdd; | |
687 | + | |
688 | + voltdm = tdvfs_info->voltdm; | |
689 | + if (IS_ERR_OR_NULL(voltdm)) { | |
690 | + dev_err(target_dev, "%s: bad voltdm\n", __func__); | |
691 | + return -EINVAL; | |
692 | + } | |
693 | + vdd = voltdm->vdd; | |
694 | + | |
695 | + /* Find the highest voltage being requested */ | |
696 | + node = plist_last(&tdvfs_info->vdd_user_list); | |
697 | + new_volt = node->prio; | |
698 | + | |
699 | + curr_volt = voltdm_get_voltage(voltdm); | |
700 | + | |
701 | + /* Disable smartreflex module across voltage and frequency scaling */ | |
702 | + omap_sr_disable(voltdm); | |
703 | + | |
704 | + if (curr_volt == new_volt) { | |
705 | + volt_scale_dir = DVFS_VOLT_SCALE_NONE; | |
706 | + } else if (curr_volt < new_volt) { | |
707 | + ret = voltdm_scale(voltdm, new_volt); | |
708 | + if (ret) { | |
709 | + dev_err(target_dev, | |
710 | + "%s: Unable to scale the %s to %ld volt\n", | |
711 | + __func__, voltdm->name, new_volt); | |
712 | + goto out; | |
713 | + } | |
714 | + volt_scale_dir = DVFS_VOLT_SCALE_UP; | |
715 | + } | |
716 | + | |
717 | + /* if we fail scale for dependent domains, go back to prev state */ | |
718 | + ret = _dep_scan_domains(target_dev, vdd, new_volt); | |
719 | + if (ret) { | |
720 | + dev_err(target_dev, | |
721 | + "%s: Error in scan domains for vdd_%s\n", | |
722 | + __func__, voltdm->name); | |
723 | + goto fail; | |
724 | + } | |
725 | + | |
726 | + /* unless we are moving down, scale dependents before we shift freq */ | |
727 | + if (!(DVFS_VOLT_SCALE_DOWN == volt_scale_dir)) { | |
728 | + ret = _dep_scale_domains(target_dev, vdd); | |
729 | + if (ret) { | |
730 | + dev_err(target_dev, | |
731 | + "%s: Error(%d)scale dependent with %ld volt\n", | |
732 | + __func__, ret, new_volt); | |
733 | + goto fail; | |
734 | + } | |
735 | + } | |
736 | + | |
737 | + /* Move all devices in list to the required frequencies */ | |
738 | + list_for_each_entry(temp_dev, &tdvfs_info->dev_list, node) { | |
739 | + struct device *dev; | |
740 | + struct opp *opp; | |
741 | + unsigned long freq = 0; | |
742 | + int r; | |
743 | + | |
744 | + dev = temp_dev->dev; | |
745 | + if (!plist_head_empty(&temp_dev->freq_user_list)) { | |
746 | + node = plist_last(&temp_dev->freq_user_list); | |
747 | + freq = node->prio; | |
748 | + } else { | |
749 | + /* dep domain? we'd probably have a voltage request */ | |
750 | + rcu_read_lock(); | |
751 | + opp = _volt_to_opp(dev, new_volt); | |
752 | + if (!IS_ERR(opp)) | |
753 | + freq = opp_get_freq(opp); | |
754 | + rcu_read_unlock(); | |
755 | + if (!freq) | |
756 | + continue; | |
757 | + } | |
758 | + | |
759 | + if (freq == clk_get_rate(temp_dev->clk)) { | |
760 | + dev_dbg(dev, "%s: Already at the requested" | |
761 | + "rate %ld\n", __func__, freq); | |
762 | + continue; | |
763 | + } | |
764 | + | |
765 | + r = clk_set_rate(temp_dev->clk, freq); | |
766 | + if (r < 0) { | |
767 | + dev_err(dev, "%s: clk set rate frq=%ld failed(%d)\n", | |
768 | + __func__, freq, r); | |
769 | + ret = r; | |
770 | + } | |
771 | + } | |
772 | + | |
773 | + if (ret) | |
774 | + goto fail; | |
775 | + | |
776 | + if (DVFS_VOLT_SCALE_DOWN == volt_scale_dir) { | |
777 | + voltdm_scale(voltdm, new_volt); | |
778 | + _dep_scale_domains(target_dev, vdd); | |
779 | + } | |
780 | + | |
781 | + /* All clear.. go out gracefully */ | |
782 | + goto out; | |
783 | + | |
784 | +fail: | |
785 | + pr_warning("%s: domain%s: No clean recovery available! could be bad!\n", | |
786 | + __func__, voltdm->name); | |
787 | +out: | |
788 | + /* Re-enable Smartreflex module */ | |
789 | + omap_sr_enable(voltdm); | |
790 | + | |
791 | + return ret; | |
792 | +} | |
793 | + | |
794 | +/* Public functions */ | |
795 | + | |
796 | +/** | |
797 | + * omap_device_scale() - Set a new rate at which the device is to operate | |
798 | + * @req_dev: pointer to the device requesting the scaling. | |
799 | + * @target_dev: pointer to the device that is to be scaled | |
800 | + * @rate: the rnew rate for the device. | |
801 | + * | |
802 | + * This API gets the device opp table associated with this device and | |
803 | + * tries putting the device to the requested rate and the voltage domain | |
804 | + * associated with the device to the voltage corresponding to the | |
805 | + * requested rate. Since multiple devices can be assocciated with a | |
806 | + * voltage domain this API finds out the possible voltage the | |
807 | + * voltage domain can enter and then decides on the final device | |
808 | + * rate. | |
809 | + * | |
810 | + * Return 0 on success else the error value | |
811 | + */ | |
812 | +int omap_device_scale(struct device *req_dev, struct device *target_dev, | |
813 | + unsigned long rate) | |
814 | +{ | |
815 | + struct opp *opp; | |
816 | + unsigned long volt, freq = rate; | |
817 | + struct omap_vdd_dvfs_info *tdvfs_info; | |
818 | + struct platform_device *pdev; | |
819 | + int ret = 0; | |
820 | + | |
821 | + pdev = container_of(target_dev, struct platform_device, dev); | |
822 | + if (IS_ERR_OR_NULL(pdev)) { | |
823 | + pr_err("%s: pdev is null!\n", __func__); | |
824 | + return -EINVAL; | |
825 | + } | |
826 | + | |
827 | + /* Lock me to ensure cross domain scaling is secure */ | |
828 | + mutex_lock(&omap_dvfs_lock); | |
829 | + | |
830 | + rcu_read_lock(); | |
831 | + opp = opp_find_freq_ceil(target_dev, &freq); | |
832 | + if (IS_ERR(opp)) { | |
833 | + rcu_read_unlock(); | |
834 | + dev_err(target_dev, "%s: Unable to find OPP for freq%ld\n", | |
835 | + __func__, rate); | |
836 | + ret = -ENODEV; | |
837 | + goto out; | |
838 | + } | |
839 | + volt = opp_get_voltage(opp); | |
840 | + rcu_read_unlock(); | |
841 | + | |
842 | + tdvfs_info = _dev_to_dvfs_info(target_dev); | |
843 | + if (IS_ERR_OR_NULL(tdvfs_info)) { | |
844 | + dev_err(target_dev, "%s: (req=%s) no vdd![f=%ld, v=%ld]\n", | |
845 | + __func__, dev_name(req_dev), freq, volt); | |
846 | + ret = -ENODEV; | |
847 | + goto out; | |
848 | + } | |
849 | + | |
850 | + ret = _add_freq_request(tdvfs_info, req_dev, target_dev, freq); | |
851 | + if (ret) { | |
852 | + dev_err(target_dev, "%s: freqadd(%s) failed %d[f=%ld, v=%ld]\n", | |
853 | + __func__, dev_name(req_dev), ret, freq, volt); | |
854 | + goto out; | |
855 | + } | |
856 | + | |
857 | + ret = _add_vdd_user(tdvfs_info, req_dev, volt); | |
858 | + if (ret) { | |
859 | + dev_err(target_dev, "%s: vddadd(%s) failed %d[f=%ld, v=%ld]\n", | |
860 | + __func__, dev_name(req_dev), ret, freq, volt); | |
861 | + _remove_freq_request(tdvfs_info, req_dev, | |
862 | + target_dev); | |
863 | + goto out; | |
864 | + } | |
865 | + | |
866 | + /* Do the actual scaling */ | |
867 | + ret = _dvfs_scale(req_dev, target_dev, tdvfs_info); | |
868 | + if (ret) { | |
869 | + dev_err(target_dev, "%s: scale by %s failed %d[f=%ld, v=%ld]\n", | |
870 | + __func__, dev_name(req_dev), ret, freq, volt); | |
871 | + _remove_freq_request(tdvfs_info, req_dev, | |
872 | + target_dev); | |
873 | + _remove_vdd_user(tdvfs_info, target_dev); | |
874 | + /* Fall through */ | |
875 | + } | |
876 | + /* Fall through */ | |
877 | +out: | |
878 | + mutex_unlock(&omap_dvfs_lock); | |
879 | + return ret; | |
880 | +} | |
881 | +EXPORT_SYMBOL(omap_device_scale); | |
882 | + | |
883 | +/** | |
884 | + * omap_dvfs_register_device - Add a parent device into dvfs managed list | |
885 | + * @dev: Device to be added | |
886 | + * @voltdm_name: Name of the voltage domain for the device | |
887 | + * @clk_name: Name of the clock for the device | |
888 | + * | |
889 | + * This function adds a given device into user_list of corresponding | |
890 | + * vdd's omap_vdd_dvfs_info strucure. This list is traversed to scale | |
891 | + * frequencies of all the devices on a given vdd. | |
892 | + * | |
893 | + * Returns 0 on success. | |
894 | + */ | |
895 | +int __init omap_dvfs_register_device(struct device *dev, char *voltdm_name, | |
896 | + char *clk_name) | |
897 | +{ | |
898 | + struct omap_vdd_dev_list *temp_dev; | |
899 | + struct omap_vdd_dvfs_info *dvfs_info; | |
900 | + struct clk *clk = NULL; | |
901 | + int ret = 0; | |
902 | + | |
903 | + if (!voltdm_name) { | |
904 | + dev_err(dev, "%s: Bad voltdm name!\n", __func__); | |
905 | + return -EINVAL; | |
906 | + } | |
907 | + if (!clk_name) { | |
908 | + dev_err(dev, "%s: Bad clk name!\n", __func__); | |
909 | + return -EINVAL; | |
910 | + } | |
911 | + | |
912 | + /* Lock me to secure structure changes */ | |
913 | + mutex_lock(&omap_dvfs_lock); | |
914 | + | |
915 | + dvfs_info = _dev_to_dvfs_info(dev); | |
916 | + if (!dvfs_info) { | |
917 | + dvfs_info = kzalloc(sizeof(struct omap_vdd_dvfs_info), | |
918 | + GFP_KERNEL); | |
919 | + if (!dvfs_info) { | |
920 | + dev_warn(dev, "%s: unable to alloc memory!\n", | |
921 | + __func__); | |
922 | + ret = -ENOMEM; | |
923 | + goto out; | |
924 | + } | |
925 | + dvfs_info->voltdm = voltdm_lookup(voltdm_name); | |
926 | + if (!dvfs_info->voltdm) { | |
927 | + dev_warn(dev, "%s: unable to find voltdm %s!\n", | |
928 | + __func__, voltdm_name); | |
929 | + kfree(dvfs_info); | |
930 | + ret = -EINVAL; | |
931 | + goto out; | |
932 | + } | |
933 | + | |
934 | + /* Init the plist */ | |
935 | + spin_lock_init(&dvfs_info->user_lock); | |
936 | + plist_head_init(&dvfs_info->vdd_user_list); | |
937 | + | |
938 | + /* Init the device list */ | |
939 | + INIT_LIST_HEAD(&dvfs_info->dev_list); | |
940 | + | |
941 | + list_add(&dvfs_info->node, &omap_dvfs_info_list); | |
942 | + } | |
943 | + | |
944 | + /* If device already added, we dont need to do more.. */ | |
945 | + list_for_each_entry(temp_dev, &dvfs_info->dev_list, node) { | |
946 | + if (temp_dev->dev == dev) | |
947 | + goto out; | |
948 | + } | |
949 | + | |
950 | + temp_dev = kzalloc(sizeof(struct omap_vdd_dev_list), GFP_KERNEL); | |
951 | + if (!temp_dev) { | |
952 | + dev_err(dev, "%s: Unable to creat a new device for vdd_%s\n", | |
953 | + __func__, dvfs_info->voltdm->name); | |
954 | + ret = -ENOMEM; | |
955 | + goto out; | |
956 | + } | |
957 | + | |
958 | + clk = clk_get(dev, clk_name); | |
959 | + if (IS_ERR_OR_NULL(clk)) { | |
960 | + dev_warn(dev, "%s: Bad clk pointer!\n", __func__); | |
961 | + kfree(temp_dev); | |
962 | + ret = -EINVAL; | |
963 | + goto out; | |
964 | + } | |
965 | + | |
966 | + /* Initialize priority ordered list */ | |
967 | + spin_lock_init(&temp_dev->user_lock); | |
968 | + plist_head_init(&temp_dev->freq_user_list); | |
969 | + | |
970 | + temp_dev->dev = dev; | |
971 | + temp_dev->clk = clk; | |
972 | + list_add_tail(&temp_dev->node, &dvfs_info->dev_list); | |
973 | + | |
974 | + /* Fall through */ | |
975 | +out: | |
976 | + mutex_unlock(&omap_dvfs_lock); | |
977 | + return ret; | |
978 | +} |
arch/arm/mach-omap2/dvfs.h
1 | +/* | |
2 | + * OMAP3/OMAP4 DVFS Management Routines | |
3 | + * | |
4 | + * Author: Vishwanath BS <vishwanath.bs@ti.com> | |
5 | + * | |
6 | + * Copyright (C) 2011 Texas Instruments, Inc. | |
7 | + * Vishwanath BS <vishwanath.bs@ti.com> | |
8 | + * | |
9 | + * This program is free software; you can redistribute it and/or modify | |
10 | + * it under the terms of the GNU General Public License version 2 as | |
11 | + * published by the Free Software Foundation. | |
12 | + */ | |
13 | + | |
14 | +#ifndef __ARCH_ARM_MACH_OMAP2_DVFS_H | |
15 | +#define __ARCH_ARM_MACH_OMAP2_DVFS_H | |
16 | +#include <plat/omap_hwmod.h> | |
17 | +#include "voltage.h" | |
18 | + | |
19 | +#ifdef CONFIG_PM | |
20 | +int omap_dvfs_register_device(struct device *dev, char *voltdm_name, | |
21 | + char *clk_name); | |
22 | +int omap_device_scale(struct device *req_dev, struct device *target_dev, | |
23 | + unsigned long rate); | |
24 | +#else | |
25 | +static inline int omap_dvfs_register_device(struct omap_hwmod *oh, | |
26 | + struct device *dev) | |
27 | +static inline int omap_dvfs_register_device(struct device *dev, | |
28 | + char *voltdm_name, char *clk_name) | |
29 | +{ | |
30 | + return -EINVAL; | |
31 | +} | |
32 | +static inline int omap_device_scale(struct device *req_dev, | |
33 | + struct device *target_dev, unsigned long rate) | |
34 | +{ | |
35 | + return -EINVAL; | |
36 | +} | |
37 | +#endif | |
38 | +#endif |