1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
|
/*
* omap-pm.c - OMAP power management interface
*
* Copyright (C) 2008-2011 Texas Instruments, Inc.
* Copyright (C) 2008-2009 Nokia Corporation
* Vishwanath BS
*
* This code is based on plat-omap/omap-pm-noop.c.
*
* Interface developed by (in alphabetical order):
* Karthik Dasu, Tony Lindgren, Rajendra Nayak, Sakari Poussa, Veeramanikandan
* Raju, Anand Sawant, Igor Stoppa, Paul Walmsley, Richard Woodruff
*/
#undef DEBUG
#include <linux/init.h>
#include <linux/cpufreq.h>
#include <linux/device.h>
#include <linux/errno.h>
#include <linux/err.h>
#include <linux/slab.h>
#include <linux/list.h>
#include <linux/debugfs.h>
#include <linux/seq_file.h>
/* Interface documentation is in mach/omap-pm.h */
#include <plat/omap-pm.h>
#include <plat/omap_device.h>
#include <plat/common.h>
#include "../mach-omap2/powerdomain.h"
#include "../mach-omap2/dvfs.h"
#include "../mach-omap2/pm.h"
#include "omap-pm-helper.h"
struct omap_opp *dsp_opps;
struct omap_opp *mpu_opps;
struct omap_opp *l3_opps;
static DEFINE_MUTEX(bus_tput_mutex);
static DEFINE_MUTEX(mpu_tput_mutex);
static DEFINE_MUTEX(mpu_lat_mutex);
/* Used to model a Interconnect Throughput */
static struct interconnect_tput {
/* Total no of users at any point of interconnect */
u8 no_of_users;
/* List of all the current users for interconnect */
struct list_head users_list;
struct list_head node;
/* Protect interconnect throughput */
struct mutex throughput_mutex;
/* Target level for interconnect throughput */
unsigned long target_level;
} *bus_tput;
/* Used to represent a user of a interconnect throughput */
struct users {
/* Device pointer used to uniquely identify the user */
struct device *dev;
struct list_head node;
/* Current level as requested for interconnect throughput by the user */
u32 level;
};
/* Private/Internal Functions */
/**
* user_lookup - look up a user by its device pointer, return a pointer
* @dev: The device to be looked up
*
* Looks for a interconnect user by its device pointer. Returns a
* pointer to
* the struct users if found, else returns NULL.
*/
static struct users *user_lookup(struct device *dev)
{
struct users *usr, *tmp_usr;
usr = NULL;
list_for_each_entry(tmp_usr, &bus_tput->users_list, node) {
if (tmp_usr->dev == dev) {
usr = tmp_usr;
break;
}
}
return usr;
}
/**
* get_user - gets a new users_list struct dynamically
*
* This function allocates dynamcially the user node
* Returns a pointer to users struct on success. On dynamic allocation
* failure
* returns a ERR_PTR(-ENOMEM).
*/
static struct users *get_user(void)
{
struct users *user;
user = kmalloc(sizeof(struct users), GFP_KERNEL);
if (!user) {
pr_err("%s FATAL ERROR: kmalloc failed\n", __func__);
return ERR_PTR(-ENOMEM);
}
return user;
}
#ifdef CONFIG_PM_DEBUG
static int pm_dbg_show_tput(struct seq_file *s, void *unused)
{
struct users *usr;
mutex_lock(&bus_tput->throughput_mutex);
list_for_each_entry(usr, &bus_tput->users_list, node)
seq_printf(s, "%s: %u\n", dev_name(usr->dev),
usr->level);
mutex_unlock(&bus_tput->throughput_mutex);
return 0;
}
static int pm_dbg_open(struct inode *inode, struct file *file)
{
return single_open(file, pm_dbg_show_tput,
&inode->i_private);
}
static const struct file_operations tputdebugfs_fops = {
.open = pm_dbg_open,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
};
#endif
/**
* omap_bus_tput_init - Initializes the interconnect throughput
* userlist
* Allocates memory for global throughput variable dynamically.
* Intializes Userlist, no. of users and throughput target level.
* Returns 0 on sucess, else returns EINVAL if memory
* allocation fails.
*/
static int __init omap_bus_tput_init(void)
{
bus_tput = kmalloc(sizeof(struct interconnect_tput), GFP_KERNEL);
if (!bus_tput) {
pr_err("%s FATAL ERROR: kmalloc failed\n", __func__);
return -EINVAL;
}
INIT_LIST_HEAD(&bus_tput->users_list);
mutex_init(&bus_tput->throughput_mutex);
bus_tput->no_of_users = 0;
bus_tput->target_level = 0;
#ifdef CONFIG_PM_DEBUG
(void) debugfs_create_file("tput", S_IRUGO,
NULL, (void *)bus_tput, &tputdebugfs_fops);
#endif
return 0;
}
/**
* add_req_tput - Request for a required level by a device
* @dev: Uniquely identifes the caller
* @level: The requested level for the interconnect bandwidth in KiB/s
*
* This function recomputes the target level of the interconnect
* bandwidth
* based on the level requested by all the users.
* Multiple calls to this function by the same device will
* replace the previous level requested
* Returns the updated level of interconnect throughput.
* In case of Invalid dev or user pointer, it returns 0.
*/
static unsigned long add_req_tput(struct device *dev, unsigned long level)
{
int ret;
struct users *user;
if (!dev) {
pr_err("Invalid dev pointer\n");
ret = 0;
}
mutex_lock(&bus_tput->throughput_mutex);
user = user_lookup(dev);
if (user == NULL) {
user = get_user();
if (IS_ERR(user)) {
pr_err("Couldn't get user from the list to"
"add new throughput constraint");
ret = 0;
goto unlock;
}
bus_tput->target_level += level;
bus_tput->no_of_users++;
user->dev = dev;
list_add(&user->node, &bus_tput->users_list);
user->level = level;
} else {
bus_tput->target_level -= user->level;
bus_tput->target_level += level;
user->level = level;
}
ret = bus_tput->target_level;
unlock:
mutex_unlock(&bus_tput->throughput_mutex);
return ret;
}
/**
* remove_req_tput - Release a previously requested level of
* a throughput level for interconnect
* @dev: Device pointer to dev
*
* This function recomputes the target level of the interconnect
* throughput after removing
* the level requested by the user.
* Returns 0, if the dev structure is invalid
* else returns modified interconnect throughput rate.
*/
static unsigned long remove_req_tput(struct device *dev)
{
struct platform_device *pdev;
struct users *user;
int found = 0;
int ret;
pdev = container_of(dev, struct platform_device, dev);
mutex_lock(&bus_tput->throughput_mutex);
list_for_each_entry(user, &bus_tput->users_list, node) {
if (user->dev == dev) {
found = 1;
break;
}
}
if (!found) {
/* No such user exists */
pr_err("OMAP-PM: Invalid Device Structure for %s\n", pdev->name);
ret = 0;
goto unlock;
}
bus_tput->target_level -= user->level;
bus_tput->no_of_users--;
list_del(&user->node);
kfree(user);
ret = bus_tput->target_level;
unlock:
mutex_unlock(&bus_tput->throughput_mutex);
return ret;
}
/**
* get_tput_level - Gets the current requested interconnect bandwidth in KiB/s
*
* Returns the current level of interconnect throughput.
*
*/
static unsigned long get_tput_level(void)
{
unsigned long ret;
mutex_lock(&bus_tput->throughput_mutex);
ret = bus_tput->target_level;
mutex_unlock(&bus_tput->throughput_mutex);
return ret;
}
int omap_pm_apply_min_bus_tput_helper_l(void)
{
int ret;
unsigned long target_level;
struct device *l3_dev;
static struct device dummy_l3_dev = {
.init_name = "omap_pm_set_min_bus_tput",
};
l3_dev = omap2_get_l3_device();
if (!l3_dev) {
pr_err("Unable to get l3 device pointer");
return -EINVAL;
}
target_level = get_tput_level();
/* Convert the throughput(in KiB/s) into Hz. */
target_level = (target_level * 1000) / 4;
ret = omap_device_scale(&dummy_l3_dev, l3_dev, target_level);
if (ret)
#ifdef CONFIG_OMAP4_DPLL_CASCADING
pr_debug("Failed: change interconnect bandwidth to %ld\n",
target_level);
#else
pr_err("Failed: change interconnect bandwidth to %ld\n",
target_level);
#endif
return ret;
}
int omap_pm_apply_min_bus_tput_helper(void)
{
int ret;
mutex_lock(&bus_tput_mutex);
ret = omap_pm_apply_min_bus_tput_helper_l();
mutex_unlock(&bus_tput_mutex);
return ret;
}
int omap_pm_set_min_bus_tput_helper(struct device *dev, u8 agent_id, long r)
{
int ret = 0;
mutex_lock(&bus_tput_mutex);
if (r == -1)
remove_req_tput(dev);
else
add_req_tput(dev, r);
/* Don't bother to attempt the device scale if the power management
* subsystem has not been initialized yet since it will just fail. PM
* will call back after it has come up to enforce any pending tput
* constraints.
*/
if (omap_pm_is_ready())
ret = omap_pm_apply_min_bus_tput_helper_l();
mutex_unlock(&bus_tput_mutex);
return ret;
}
int omap_pm_set_max_dev_wakeup_lat_helper(struct device *req_dev,
struct device *dev, long t)
{
struct omap_device *odev;
struct powerdomain *pwrdm_dev;
struct platform_device *pdev;
int ret = 0;
if (!req_dev || !dev || t < -1) {
WARN(1, "OMAP PM: %s: invalid parameter(s)", __func__);
return -EINVAL;
};
/* Look for the devices Power Domain */
pdev = container_of(dev, struct platform_device, dev);
/* Try to catch non platform devices. */
if (pdev->name == NULL) {
pr_err("OMAP-PM: Error: platform device not valid\n");
return -EINVAL;
}
odev = to_omap_device(pdev);
if (odev) {
pwrdm_dev = omap_device_get_pwrdm(odev);
} else {
pr_err("OMAP-PM: Error: Could not find omap_device for %s\n",
pdev->name);
return -EINVAL;
}
/* Catch devices with undefined powerdomains. */
if (!pwrdm_dev) {
pr_err("OMAP-PM: Error: could not find parent pwrdm for %s\n",
pdev->name);
return -EINVAL;
}
if (t == -1)
ret = pwrdm_wakeuplat_release_constraint(pwrdm_dev, req_dev);
else
ret = pwrdm_wakeuplat_set_constraint(pwrdm_dev, req_dev, t);
return ret;
}
/* Must be called after clock framework is initialized */
int __init omap_pm_if_init_helper(void)
{
int ret;
ret = omap_bus_tput_init();
if (ret)
pr_err("Failed: init of interconnect bandwidth users list\n");
return ret;
}
|