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