diff options
Diffstat (limited to 'arch/arm/mach-tegra/sysfs-cluster.c')
-rw-r--r-- | arch/arm/mach-tegra/sysfs-cluster.c | 461 |
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 | |||
110 | static struct kobject *cluster_kobj; | ||
111 | static spinlock_t cluster_lock; | ||
112 | static unsigned int flags = 0; | ||
113 | static unsigned int wake_ms = 0; | ||
114 | |||
115 | static ssize_t sysfscluster_show(struct kobject *kobj, | ||
116 | struct kobj_attribute *attr, char *buf); | ||
117 | |||
118 | static 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" */ | ||
122 | static struct kobj_attribute cluster_active_attr = | ||
123 | __ATTR(active, 0640, sysfscluster_show, sysfscluster_store); | ||
124 | |||
125 | /* Immediate wake-up when performing switch: 0, 1 */ | ||
126 | static 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 */ | ||
130 | static struct kobj_attribute cluster_force_attr = | ||
131 | __ATTR(force, 0640, sysfscluster_show, sysfscluster_store); | ||
132 | |||
133 | /* Wake time (in milliseconds) */ | ||
134 | static 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 */ | ||
139 | static unsigned int power_mode = 2; | ||
140 | static struct kobj_attribute cluster_powermode_attr = | ||
141 | __ATTR(power_mode, 0640, sysfscluster_show, sysfscluster_store); | ||
142 | #endif | ||
143 | |||
144 | #if DEBUG_CLUSTER_SWITCH | ||
145 | unsigned int tegra_cluster_debug = 0; | ||
146 | static struct kobj_attribute cluster_debug_attr = | ||
147 | __ATTR(debug, 0640, sysfscluster_show, sysfscluster_store); | ||
148 | #endif | ||
149 | |||
150 | typedef 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 | |||
165 | static 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 | |||
187 | static 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 | |||
236 | static 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 | } | ||
392 | fail: | ||
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 | |||
407 | static 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 | |||
434 | fail: | ||
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 | |||
442 | static 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 | |||
459 | module_init(sysfscluster_init); | ||
460 | module_exit(sysfscluster_exit); | ||
461 | MODULE_LICENSE("GPL"); | ||