aboutsummaryrefslogtreecommitdiffstats
path: root/arch/arm/mach-tegra/sysfs-cluster.c
diff options
context:
space:
mode:
Diffstat (limited to 'arch/arm/mach-tegra/sysfs-cluster.c')
-rw-r--r--arch/arm/mach-tegra/sysfs-cluster.c461
1 files changed, 461 insertions, 0 deletions
diff --git a/arch/arm/mach-tegra/sysfs-cluster.c b/arch/arm/mach-tegra/sysfs-cluster.c
new file mode 100644
index 00000000000..49c3abcf32b
--- /dev/null
+++ b/arch/arm/mach-tegra/sysfs-cluster.c
@@ -0,0 +1,461 @@
1/*
2 * Copyright (c) 2010-2011 NVIDIA Corporation.
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
7 *
8 * Redistributions of source code must retain the above copyright notice,
9 * this list of conditions and the following disclaimer.
10 *
11 * Redistributions in binary form must reproduce the above copyright notice,
12 * this list of conditions and the following disclaimer in the documentation
13 * and/or other materials provided with the distribution.
14 *
15 * Neither the name of the NVIDIA Corporation nor the names of its contributors
16 * may be used to endorse or promote products derived from this software
17 * without specific prior written permission.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
23 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29 * POSSIBILITY OF SUCH DAMAGE.
30 *
31 */
32
33/*
34 * This driver creates the /sys/kernel/cluster node and attributes for CPU
35 * switch testing. Node attributes:
36 *
37 * active: currently active CPU (G or LP)
38 * write: 'g' = switch to G CPU
39 * 'lp' = switch to LP CPU
40 * 'toggle' = switch to the other CPU
41 * read: returns the currently active CPU (g or lp)
42 *
43 * force: force switch even if already on target CPU
44 * write: '0' = do not perform switch if
45 * active CPU == target CPU (default)
46 * '1' = force switch regardless of
47 * currently active CPU
48 * read: returns the current status of the force flag
49 *
50 * immediate: request immediate wake-up from switch request
51 * write: '0' = non-immediate wake-up on next interrupt (default)
52 * '1' = immediate wake-up
53 * read: returns the current status of the immediate flag
54 *
55 * power_mode: power mode to use for switch (LP1 or LP2)
56 * write: '1' = use LP1 power mode
57 * '2' = use LP2 power mode (default)
58 * read: returns the current status of the immediate flag
59 *
60 * wake_ms: wake time (in milliseconds) -- ignored if immediate==1
61 * write: '0' = wake up at the next non-timer interrupt
62 * 'n' = (n > 0) wake-up after 'n' milliseconds or the
63 * next non-timer interrupt (whichever comes first)
64 * read: returns the current wake_ms value
65 *
66 * Writing the force, immediate and wake_ms attributes simply updates the
67 * state of internal variables that will be used for the next switch request.
68 * Writing to the active attribute initates a switch request using the
69 * current values of the force, immediate, and wake_ms attributes.
70 *
71 * The OS tick timer is not a valid interrupt source for waking up following
72 * a switch request. This is because the kernel uses local timers that are
73 * part of the CPU complex. These get shut down when the CPU complex is
74 * placed into reset by the switch request. If you want a timed wake up
75 * from a switch, you must specify a positive wake_ms value. This will
76 * ensure that a non-local timer is programmed to fire an interrupt
77 * after the desired interval.
78 *
79 */
80
81#include <linux/module.h>
82#include <linux/kernel.h>
83#include <linux/sysfs.h>
84#include <linux/kobject.h>
85#include <linux/smp.h>
86#include <linux/io.h>
87#include <linux/clk.h>
88
89#include <mach/iomap.h>
90#include "clock.h"
91#include "sleep.h"
92#include "pm.h"
93
94#define SYSFS_CLUSTER_PRINTS 1 /* Nonzero: enable status prints */
95#define SYSFS_CLUSTER_TRACE_PRINTS 0 /* Nonzero: enable trace prints */
96#define SYSFS_CLUSTER_POWER_MODE 1 /* Nonzero: use power modes other than LP2*/
97
98#if SYSFS_CLUSTER_TRACE_PRINTS
99#define TRACE_CLUSTER(x) printk x
100#else
101#define TRACE_CLUSTER(x)
102#endif
103
104#if SYSFS_CLUSTER_PRINTS
105#define PRINT_CLUSTER(x) printk x
106#else
107#define PRINT_CLUSTER(x)
108#endif
109
110static struct kobject *cluster_kobj;
111static spinlock_t cluster_lock;
112static unsigned int flags = 0;
113static unsigned int wake_ms = 0;
114
115static ssize_t sysfscluster_show(struct kobject *kobj,
116 struct kobj_attribute *attr, char *buf);
117
118static ssize_t sysfscluster_store(struct kobject *kobj,
119 struct kobj_attribute *attr, const char *buf, size_t count);
120
121/* Active CPU: "G", "LP", "toggle" */
122static struct kobj_attribute cluster_active_attr =
123 __ATTR(active, 0640, sysfscluster_show, sysfscluster_store);
124
125/* Immediate wake-up when performing switch: 0, 1 */
126static struct kobj_attribute cluster_immediate_attr =
127 __ATTR(immediate, 0640, sysfscluster_show, sysfscluster_store);
128
129/* Force power transition even if already on the desired CPU: 0, 1 */
130static struct kobj_attribute cluster_force_attr =
131 __ATTR(force, 0640, sysfscluster_show, sysfscluster_store);
132
133/* Wake time (in milliseconds) */
134static struct kobj_attribute cluster_wake_ms_attr =
135 __ATTR(wake_ms, 0640, sysfscluster_show, sysfscluster_store);
136
137#if defined(CONFIG_PM_SLEEP) && SYSFS_CLUSTER_POWER_MODE
138/* LPx power mode to use when switching CPUs: 1=LP1, 2=LP2 */
139static unsigned int power_mode = 2;
140static struct kobj_attribute cluster_powermode_attr =
141 __ATTR(power_mode, 0640, sysfscluster_show, sysfscluster_store);
142#endif
143
144#if DEBUG_CLUSTER_SWITCH
145unsigned int tegra_cluster_debug = 0;
146static struct kobj_attribute cluster_debug_attr =
147 __ATTR(debug, 0640, sysfscluster_show, sysfscluster_store);
148#endif
149
150typedef enum
151{
152 ClusterAttr_Invalid = 0,
153 ClusterAttr_Active,
154 ClusterAttr_Immediate,
155 ClusterAttr_Force,
156 ClusterAttr_WakeMs,
157#if defined(CONFIG_PM_SLEEP) && SYSFS_CLUSTER_POWER_MODE
158 ClusterAttr_PowerMode,
159#endif
160#if DEBUG_CLUSTER_SWITCH
161 ClusterAttr_Debug
162#endif
163} ClusterAttr;
164
165static ClusterAttr GetClusterAttr(const char *name)
166{
167 if (!strcmp(name, "active"))
168 return ClusterAttr_Active;
169 if (!strcmp(name, "immediate"))
170 return ClusterAttr_Immediate;
171 if (!strcmp(name, "force"))
172 return ClusterAttr_Force;
173 if (!strcmp(name, "wake_ms"))
174 return ClusterAttr_WakeMs;
175#if defined(CONFIG_PM_SLEEP) && SYSFS_CLUSTER_POWER_MODE
176 if (!strcmp(name, "power_mode"))
177 return ClusterAttr_PowerMode;
178#endif
179#if DEBUG_CLUSTER_SWITCH
180 if (!strcmp(name, "debug"))
181 return ClusterAttr_Debug;
182#endif
183 TRACE_CLUSTER(("GetClusterAttr(%s): invalid\n", name));
184 return ClusterAttr_Invalid;
185}
186
187static ssize_t sysfscluster_show(struct kobject *kobj,
188 struct kobj_attribute *attr, char *buf)
189{
190 ClusterAttr type;
191 ssize_t len;
192
193 TRACE_CLUSTER(("+sysfscluster_show\n"));
194
195 type = GetClusterAttr(attr->attr.name);
196 switch (type) {
197 case ClusterAttr_Active:
198 len = sprintf(buf, "%s\n", is_lp_cluster() ? "LP" : "G");
199 break;
200
201 case ClusterAttr_Immediate:
202 len = sprintf(buf, "%d\n",
203 ((flags & TEGRA_POWER_CLUSTER_IMMEDIATE) != 0));
204 break;
205
206 case ClusterAttr_Force:
207 len = sprintf(buf, "%d\n",
208 ((flags & TEGRA_POWER_CLUSTER_FORCE) != 0));
209 break;
210
211 case ClusterAttr_WakeMs:
212 len = sprintf(buf, "%d\n", wake_ms);
213 break;
214
215#if defined(CONFIG_PM_SLEEP) && SYSFS_CLUSTER_POWER_MODE
216 case ClusterAttr_PowerMode:
217 len = sprintf(buf, "%d\n", power_mode);
218 break;
219#endif
220
221#if DEBUG_CLUSTER_SWITCH
222 case ClusterAttr_Debug:
223 len = sprintf(buf, "%d\n", tegra_cluster_debug);
224 break;
225#endif
226
227 default:
228 len = sprintf(buf, "invalid\n");
229 break;
230 }
231
232 TRACE_CLUSTER(("-sysfscluster_show\n"));
233 return len;
234}
235
236static ssize_t sysfscluster_store(struct kobject *kobj,
237 struct kobj_attribute *attr, const char *buf, size_t count)
238{
239 ClusterAttr type;
240 ssize_t ret = count--;
241 unsigned request;
242 int e;
243 int tmp;
244 int cnt;
245 struct clk *cpu_clk = tegra_get_clock_by_name("cpu");
246 struct clk *cpu_g_clk = tegra_get_clock_by_name("cpu_g");
247 struct clk *cpu_lp_clk = tegra_get_clock_by_name("cpu_lp");
248 struct clk *new_parent = NULL;
249
250 if (!cpu_clk || !cpu_g_clk || !cpu_lp_clk) {
251 ret = -ENOSYS;
252 goto fail;
253 }
254
255 TRACE_CLUSTER(("+sysfscluster_store: %p, %d\n", buf, count));
256
257 /* The count includes data bytes follow by a line feed character. */
258 if (!buf || (count < 1)) {
259 ret = -EINVAL;
260 goto fail;
261 }
262
263 type = GetClusterAttr(attr->attr.name);
264
265 spin_lock(&cluster_lock);
266
267 switch (type) {
268 case ClusterAttr_Active:
269 if (!strncasecmp(buf, "g", count)) {
270 flags &= ~TEGRA_POWER_CLUSTER_MASK;
271 flags |= TEGRA_POWER_CLUSTER_G;
272 } else if (!strncasecmp(buf, "lp", count)) {
273 flags &= ~TEGRA_POWER_CLUSTER_MASK;
274 flags |= TEGRA_POWER_CLUSTER_LP;
275 } else if (!strncasecmp(buf, "toggle", count)) {
276 flags &= ~TEGRA_POWER_CLUSTER_MASK;
277 if (is_lp_cluster())
278 flags |= TEGRA_POWER_CLUSTER_G;
279 else
280 flags |= TEGRA_POWER_CLUSTER_LP;
281 } else {
282 PRINT_CLUSTER(("cluster/active: '%*.*s' invalid, "
283 " must be g, lp, or toggle\n",
284 count, count, buf));
285 ret = -EINVAL;
286 break;
287 }
288 PRINT_CLUSTER(("cluster/active -> %s\n",
289 (flags & TEGRA_POWER_CLUSTER_G) ? "G" : "LP"));
290
291 request = flags;
292#if defined(CONFIG_PM_SLEEP) && SYSFS_CLUSTER_POWER_MODE
293 if (power_mode == 1) {
294 request |= TEGRA_POWER_SDRAM_SELFREFRESH;
295 }
296#endif
297 tegra_cluster_switch_set_parameters(wake_ms * 1000, request);
298 new_parent = (flags & TEGRA_POWER_CLUSTER_LP) ?
299 cpu_lp_clk : cpu_g_clk;
300 break;
301
302 case ClusterAttr_Immediate:
303 if ((count == 1) && (*buf == '0'))
304 flags &= ~TEGRA_POWER_CLUSTER_IMMEDIATE;
305 else if ((count == 1) && *buf == '1')
306 flags |= TEGRA_POWER_CLUSTER_IMMEDIATE;
307 else {
308 PRINT_CLUSTER(("cluster/immediate: '%*.*s' invalid, "
309 "must be 0 or 1\n", count, count, buf));
310 ret = -EINVAL;
311 break;
312 }
313 PRINT_CLUSTER(("cluster/immediate -> %c\n",
314 (flags & TEGRA_POWER_CLUSTER_IMMEDIATE) ? '1' : '0'));
315 break;
316
317 case ClusterAttr_Force:
318 if ((count == 1) && (*buf == '0'))
319 flags &= ~TEGRA_POWER_CLUSTER_FORCE;
320 else if ((count == 1) && (*buf == '1'))
321 flags |= TEGRA_POWER_CLUSTER_FORCE;
322 else {
323 PRINT_CLUSTER(("cluster/force: '%*.*s' invalid, "
324 "must be 0 or 1\n", count, count, buf));
325 ret = -EINVAL;
326 break;
327 }
328 PRINT_CLUSTER(("cluster/force -> %c\n",
329 (flags & TEGRA_POWER_CLUSTER_FORCE) ? '1' : '0'));
330 break;
331
332 case ClusterAttr_WakeMs:
333 tmp = 0;
334 cnt = sscanf(buf, "%d\n", &tmp);
335 if ((cnt != 1) || (tmp < 0)) {
336 PRINT_CLUSTER(("cluster/wake_ms: '%*.*s' is invalid\n",
337 count, count, buf));
338 ret = -EINVAL;
339 break;
340 }
341 wake_ms = tmp;
342 PRINT_CLUSTER(("cluster/wake_ms -> %d\n", wake_ms));
343 break;
344
345#if defined(CONFIG_PM_SLEEP) && SYSFS_CLUSTER_POWER_MODE
346 case ClusterAttr_PowerMode:
347 if ((count == 1) && (*buf == '2'))
348 power_mode = 2;
349 else if ((count == 1) && *buf == '1')
350 power_mode = 1;
351 else {
352 PRINT_CLUSTER(("cluster/power_mode: '%*.*s' invalid, "
353 "must be 2 or 1\n", count, count, buf));
354 ret = -EINVAL;
355 break;
356 }
357 PRINT_CLUSTER(("cluster/power_mode -> %d\n", power_mode));
358 break;
359#endif
360
361#if DEBUG_CLUSTER_SWITCH
362 case ClusterAttr_Debug:
363 if ((count == 1) && (*buf == '0'))
364 tegra_cluster_debug = 0;
365 else if ((count == 1) && (*buf == '1'))
366 tegra_cluster_debug = 1;
367 else {
368 PRINT_CLUSTER(("cluster/debug: '%*.*s' invalid, "
369 "must be 0 or 1\n", count, count, buf));
370 ret = -EINVAL;
371 break;
372 }
373 PRINT_CLUSTER(("cluster/debug -> %d\n",tegra_cluster_debug));
374 break;
375#endif
376
377 default:
378 ret = -ENOENT;
379 break;
380 }
381
382 spin_unlock(&cluster_lock);
383
384 if (new_parent) {
385 e = clk_set_parent(cpu_clk, new_parent);
386 if (e) {
387 PRINT_CLUSTER(("cluster/active: request failed (%d)\n",
388 e));
389 ret = e;
390 }
391 }
392fail:
393 TRACE_CLUSTER(("-sysfscluster_store: %d\n", count));
394 return ret;
395}
396
397#define CREATE_FILE(x) \
398 do { \
399 e = sysfs_create_file(cluster_kobj, &cluster_##x##_attr.attr); \
400 if (e) { \
401 TRACE_CLUSTER(("cluster/" __stringify(x) \
402 ": sysfs_create_file failed!\n")); \
403 goto fail; \
404 } \
405 } while (0)
406
407static int __init sysfscluster_init(void)
408{
409 int e;
410
411 TRACE_CLUSTER(("+sysfscluster_init\n"));
412
413 spin_lock_init(&cluster_lock);
414 cluster_kobj = kobject_create_and_add("cluster", kernel_kobj);
415
416 CREATE_FILE(active);
417 CREATE_FILE(immediate);
418 CREATE_FILE(force);
419 CREATE_FILE(wake_ms);
420#if defined(CONFIG_PM_SLEEP) && SYSFS_CLUSTER_POWER_MODE
421 CREATE_FILE(powermode);
422#endif
423#if DEBUG_CLUSTER_SWITCH
424 CREATE_FILE(debug);
425#endif
426
427 spin_lock(&cluster_lock);
428 if (is_lp_cluster())
429 flags |= TEGRA_POWER_CLUSTER_LP;
430 else
431 flags |= TEGRA_POWER_CLUSTER_G;
432 spin_unlock(&cluster_lock);
433
434fail:
435 TRACE_CLUSTER(("-sysfscluster_init\n"));
436 return e;
437}
438
439#define REMOVE_FILE(x) \
440 sysfs_remove_file(cluster_kobj, &cluster_##x##_attr.attr)
441
442static void __exit sysfscluster_exit(void)
443{
444 TRACE_CLUSTER(("+sysfscluster_exit\n"));
445#if DEBUG_CLUSTER_SWITCH
446 REMOVE_FILE(debug);
447#endif
448#if defined(CONFIG_PM_SLEEP) && SYSFS_CLUSTER_POWER_MODE
449 REMOVE_FILE(powermode);
450#endif
451 REMOVE_FILE(wake_ms);
452 REMOVE_FILE(force);
453 REMOVE_FILE(immediate);
454 REMOVE_FILE(active);
455 kobject_del(cluster_kobj);
456 TRACE_CLUSTER(("-sysfscluster_exit\n"));
457}
458
459module_init(sysfscluster_init);
460module_exit(sysfscluster_exit);
461MODULE_LICENSE("GPL");