aboutsummaryrefslogtreecommitdiffstats
path: root/arch/arm/plat-omap/omap-pm-helper.c
blob: 2fdd7af49a4dd365a80a2891fbcbcd9e0df7616b (plain)
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
/*
 * 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 "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 users *user;
	int found = 0;
	int ret;

	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("Invalid Device Structure\n");
		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;
}

int omap_pm_set_min_bus_tput_helper(struct device *dev, u8 agent_id, long r)
{

	int ret = 0;
	struct device *l3_dev;
	static struct device dummy_l3_dev = {
		.init_name = "omap_pm_set_min_bus_tput",
	};
	unsigned long target_level = 0;

	mutex_lock(&bus_tput_mutex);

	l3_dev = omap2_get_l3_device();
	if (!l3_dev) {
		pr_err("Unable to get l3 device pointer");
		ret = -EINVAL;
		goto unlock;
	}

	if (r == -1)
		target_level = remove_req_tput(dev);
	else
		target_level = add_req_tput(dev, r);

	/* 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)
		pr_err("Failed: change interconnect bandwidth to %ld\n",
		     target_level);
unlock:
	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;
}