Merge branch 'master' of /home/sam/kernel/linux-2.6/
[linux-2.6] / arch / frv / kernel / pm.c
1 /*
2  * FR-V Power Management Routines
3  *
4  * Copyright (c) 2004 Red Hat, Inc.
5  *
6  * Based on SA1100 version:
7  * Copyright (c) 2001 Cliff Brake <cbrake@accelent.com>
8  *
9  * This program is free software; you can redistribute it and/or
10  * modify it under the terms of the GNU General Public License.
11  *
12  */
13
14 #include <linux/init.h>
15 #include <linux/module.h>
16 #include <linux/pm.h>
17 #include <linux/pm_legacy.h>
18 #include <linux/sched.h>
19 #include <linux/interrupt.h>
20 #include <linux/sysctl.h>
21 #include <linux/errno.h>
22 #include <linux/delay.h>
23 #include <asm/uaccess.h>
24
25 #include <asm/mb86943a.h>
26
27 #include "local.h"
28
29 void (*pm_power_off)(void);
30 EXPORT_SYMBOL(pm_power_off);
31
32 extern void frv_change_cmode(int);
33
34 /*
35  * Debug macros
36  */
37 #define DEBUG
38
39 int pm_do_suspend(void)
40 {
41         local_irq_disable();
42
43         __set_LEDS(0xb1);
44
45         /* go zzz */
46         frv_cpu_suspend(pdm_suspend_mode);
47
48         __set_LEDS(0xb2);
49
50         local_irq_enable();
51
52         return 0;
53 }
54
55 static unsigned long __irq_mask;
56
57 /*
58  * Setup interrupt masks, etc to enable wakeup by power switch
59  */
60 static void __default_power_switch_setup(void)
61 {
62         /* default is to mask all interrupt sources. */
63         __irq_mask = *(unsigned long *)0xfeff9820;
64         *(unsigned long *)0xfeff9820 = 0xfffe0000;
65 }
66
67 /*
68  * Cleanup interrupt masks, etc after wakeup by power switch
69  */
70 static void __default_power_switch_cleanup(void)
71 {
72         *(unsigned long *)0xfeff9820 = __irq_mask;
73 }
74
75 /*
76  * Return non-zero if wakeup irq was caused by power switch
77  */
78 static int __default_power_switch_check(void)
79 {
80         return 1;
81 }
82
83 void (*__power_switch_wake_setup)(void) = __default_power_switch_setup;
84 int  (*__power_switch_wake_check)(void) = __default_power_switch_check;
85 void (*__power_switch_wake_cleanup)(void) = __default_power_switch_cleanup;
86
87 int pm_do_bus_sleep(void)
88 {
89         local_irq_disable();
90
91         /*
92          * Here is where we need some platform-dependent setup
93          * of the interrupt state so that appropriate wakeup
94          * sources are allowed and all others are masked.
95          */
96         __power_switch_wake_setup();
97
98         __set_LEDS(0xa1);
99
100         /* go zzz
101          *
102          * This is in a loop in case power switch shares an irq with other
103          * devices. The wake_check() tells us if we need to finish waking
104          * or go back to sleep.
105          */
106         do {
107                 frv_cpu_suspend(HSR0_PDM_BUS_SLEEP);
108         } while (__power_switch_wake_check && !__power_switch_wake_check());
109
110         __set_LEDS(0xa2);
111
112         /*
113          * Here is where we need some platform-dependent restore
114          * of the interrupt state prior to being called.
115          */
116         __power_switch_wake_cleanup();
117
118         local_irq_enable();
119
120         return 0;
121 }
122
123 unsigned long sleep_phys_sp(void *sp)
124 {
125         return virt_to_phys(sp);
126 }
127
128 #ifdef CONFIG_SYSCTL
129 /*
130  * Use a temporary sysctl number. Horrid, but will be cleaned up in 2.6
131  * when all the PM interfaces exist nicely.
132  */
133 #define CTL_PM 9899
134 #define CTL_PM_SUSPEND 1
135 #define CTL_PM_CMODE 2
136 #define CTL_PM_P0 4
137 #define CTL_PM_CM 5
138
139 static int user_atoi(char __user *ubuf, size_t len)
140 {
141         char buf[16];
142         unsigned long ret;
143
144         if (len > 15)
145                 return -EINVAL;
146
147         if (copy_from_user(buf, ubuf, len))
148                 return -EFAULT;
149
150         buf[len] = 0;
151         ret = simple_strtoul(buf, NULL, 0);
152         if (ret > INT_MAX)
153                 return -ERANGE;
154         return ret;
155 }
156
157 /*
158  * Send us to sleep.
159  */
160 static int sysctl_pm_do_suspend(ctl_table *ctl, int write, struct file *filp,
161                                 void __user *buffer, size_t *lenp, loff_t *fpos)
162 {
163         int retval, mode;
164
165         if (*lenp <= 0)
166                 return -EIO;
167
168         mode = user_atoi(buffer, *lenp);
169         if ((mode != 1) && (mode != 5))
170                 return -EINVAL;
171
172         retval = pm_send_all(PM_SUSPEND, (void *)3);
173
174         if (retval == 0) {
175                 if (mode == 5)
176                     retval = pm_do_bus_sleep();
177                 else
178                     retval = pm_do_suspend();
179                 pm_send_all(PM_RESUME, (void *)0);
180         }
181
182         return retval;
183 }
184
185 static int try_set_cmode(int new_cmode)
186 {
187         if (new_cmode > 15)
188                 return -EINVAL;
189         if (!(clock_cmodes_permitted & (1<<new_cmode)))
190                 return -EINVAL;
191
192         /* tell all the drivers we're suspending */
193         pm_send_all(PM_SUSPEND, (void *)3);
194
195         /* now change cmode */
196         local_irq_disable();
197         frv_dma_pause_all();
198
199         frv_change_cmode(new_cmode);
200
201         determine_clocks(0);
202         time_divisor_init();
203
204 #ifdef DEBUG
205         determine_clocks(1);
206 #endif
207         frv_dma_resume_all();
208         local_irq_enable();
209
210         /* tell all the drivers we're resuming */
211         pm_send_all(PM_RESUME, (void *)0);
212         return 0;
213 }
214
215
216 static int cmode_procctl(ctl_table *ctl, int write, struct file *filp,
217                          void __user *buffer, size_t *lenp, loff_t *fpos)
218 {
219         int new_cmode;
220
221         if (!write)
222                 return proc_dointvec(ctl, write, filp, buffer, lenp, fpos);
223
224         new_cmode = user_atoi(buffer, *lenp);
225
226         return try_set_cmode(new_cmode)?:*lenp;
227 }
228
229 static int cmode_sysctl(ctl_table *table, int __user *name, int nlen,
230                         void __user *oldval, size_t __user *oldlenp,
231                         void __user *newval, size_t newlen, void **context)
232 {
233         if (oldval && oldlenp) {
234                 size_t oldlen;
235
236                 if (get_user(oldlen, oldlenp))
237                         return -EFAULT;
238
239                 if (oldlen != sizeof(int))
240                         return -EINVAL;
241
242                 if (put_user(clock_cmode_current, (unsigned __user *)oldval) ||
243                     put_user(sizeof(int), oldlenp))
244                         return -EFAULT;
245         }
246         if (newval && newlen) {
247                 int new_cmode;
248
249                 if (newlen != sizeof(int))
250                         return -EINVAL;
251
252                 if (get_user(new_cmode, (int __user *)newval))
253                         return -EFAULT;
254
255                 return try_set_cmode(new_cmode)?:1;
256         }
257         return 1;
258 }
259
260 static int try_set_p0(int new_p0)
261 {
262         unsigned long flags, clkc;
263
264         if (new_p0 < 0 || new_p0 > 1)
265                 return -EINVAL;
266
267         local_irq_save(flags);
268         __set_PSR(flags & ~PSR_ET);
269
270         frv_dma_pause_all();
271
272         clkc = __get_CLKC();
273         if (new_p0)
274                 clkc |= CLKC_P0;
275         else
276                 clkc &= ~CLKC_P0;
277         __set_CLKC(clkc);
278
279         determine_clocks(0);
280         time_divisor_init();
281
282 #ifdef DEBUG
283         determine_clocks(1);
284 #endif
285         frv_dma_resume_all();
286         local_irq_restore(flags);
287         return 0;
288 }
289
290 static int try_set_cm(int new_cm)
291 {
292         unsigned long flags, clkc;
293
294         if (new_cm < 0 || new_cm > 1)
295                 return -EINVAL;
296
297         local_irq_save(flags);
298         __set_PSR(flags & ~PSR_ET);
299
300         frv_dma_pause_all();
301
302         clkc = __get_CLKC();
303         clkc &= ~CLKC_CM;
304         clkc |= new_cm;
305         __set_CLKC(clkc);
306
307         determine_clocks(0);
308         time_divisor_init();
309
310 #if 1 //def DEBUG
311         determine_clocks(1);
312 #endif
313
314         frv_dma_resume_all();
315         local_irq_restore(flags);
316         return 0;
317 }
318
319 static int p0_procctl(ctl_table *ctl, int write, struct file *filp,
320                       void __user *buffer, size_t *lenp, loff_t *fpos)
321 {
322         int new_p0;
323
324         if (!write)
325                 return proc_dointvec(ctl, write, filp, buffer, lenp, fpos);
326
327         new_p0 = user_atoi(buffer, *lenp);
328
329         return try_set_p0(new_p0)?:*lenp;
330 }
331
332 static int p0_sysctl(ctl_table *table, int __user *name, int nlen,
333                      void __user *oldval, size_t __user *oldlenp,
334                      void __user *newval, size_t newlen, void **context)
335 {
336         if (oldval && oldlenp) {
337                 size_t oldlen;
338
339                 if (get_user(oldlen, oldlenp))
340                         return -EFAULT;
341
342                 if (oldlen != sizeof(int))
343                         return -EINVAL;
344
345                 if (put_user(clock_p0_current, (unsigned __user *)oldval) ||
346                     put_user(sizeof(int), oldlenp))
347                         return -EFAULT;
348         }
349         if (newval && newlen) {
350                 int new_p0;
351
352                 if (newlen != sizeof(int))
353                         return -EINVAL;
354
355                 if (get_user(new_p0, (int __user *)newval))
356                         return -EFAULT;
357
358                 return try_set_p0(new_p0)?:1;
359         }
360         return 1;
361 }
362
363 static int cm_procctl(ctl_table *ctl, int write, struct file *filp,
364                       void __user *buffer, size_t *lenp, loff_t *fpos)
365 {
366         int new_cm;
367
368         if (!write)
369                 return proc_dointvec(ctl, write, filp, buffer, lenp, fpos);
370
371         new_cm = user_atoi(buffer, *lenp);
372
373         return try_set_cm(new_cm)?:*lenp;
374 }
375
376 static int cm_sysctl(ctl_table *table, int __user *name, int nlen,
377                      void __user *oldval, size_t __user *oldlenp,
378                      void __user *newval, size_t newlen, void **context)
379 {
380         if (oldval && oldlenp) {
381                 size_t oldlen;
382
383                 if (get_user(oldlen, oldlenp))
384                         return -EFAULT;
385
386                 if (oldlen != sizeof(int))
387                         return -EINVAL;
388
389                 if (put_user(clock_cm_current, (unsigned __user *)oldval) ||
390                     put_user(sizeof(int), oldlenp))
391                         return -EFAULT;
392         }
393         if (newval && newlen) {
394                 int new_cm;
395
396                 if (newlen != sizeof(int))
397                         return -EINVAL;
398
399                 if (get_user(new_cm, (int __user *)newval))
400                         return -EFAULT;
401
402                 return try_set_cm(new_cm)?:1;
403         }
404         return 1;
405 }
406
407
408 static struct ctl_table pm_table[] =
409 {
410         {CTL_PM_SUSPEND, "suspend", NULL, 0, 0200, NULL, &sysctl_pm_do_suspend},
411         {CTL_PM_CMODE, "cmode", &clock_cmode_current, sizeof(int), 0644, NULL, &cmode_procctl, &cmode_sysctl, NULL},
412         {CTL_PM_P0, "p0", &clock_p0_current, sizeof(int), 0644, NULL, &p0_procctl, &p0_sysctl, NULL},
413         {CTL_PM_CM, "cm", &clock_cm_current, sizeof(int), 0644, NULL, &cm_procctl, &cm_sysctl, NULL},
414         {0}
415 };
416
417 static struct ctl_table pm_dir_table[] =
418 {
419         {CTL_PM, "pm", NULL, 0, 0555, pm_table},
420         {0}
421 };
422
423 /*
424  * Initialize power interface
425  */
426 static int __init pm_init(void)
427 {
428         register_sysctl_table(pm_dir_table, 1);
429         return 0;
430 }
431
432 __initcall(pm_init);
433
434 #endif