arm: tegra: sd: enable sd dpd
[linux-2.6.git] / arch / arm / mach-tegra / sysfs-cluster.c
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");