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