diff options
author | Manuel Lauss <mano@roarinelk.homelinux.net> | 2008-12-21 03:26:27 -0500 |
---|---|---|
committer | Ralf Baechle <ralf@linux-mips.org> | 2009-01-11 04:57:27 -0500 |
commit | 61f9c58da57a80b0df1ced18a28cbbaebd4d417a (patch) | |
tree | cbeaa2353d98899e88c6d44dc8d1a16eabbb6a47 /arch/mips/alchemy | |
parent | ac15dad061d351281b0bafbae1ecdd84e601435a (diff) |
MIPS: Alchemy: new userspace suspend interface for development boards.
Replace the current sysctl-based suspend interface with a new sysfs-
based one which also uses the Linux-2.6 suspend model.
To configure wakeup sources, a subtree for the demoboards is created
under /sys/power/db1x:
sys/
`-- power
`-- db1x
|-- gpio0
|-- gpio1
|-- gpio2
|-- gpio3
|-- gpio4
|-- gpio5
|-- gpio6
|-- gpio7
|-- timer
|-- timer_timeout
|-- wakemsk
`-- wakesrc
The nodes 'gpio[0-7]' and 'timer' configure the GPIO0..7 and M2
bits of the SYS_WAKEMSK (wakeup source enable) register. Writing '1'
enables a wakesource, 0 disables it.
The 'timer_timeout' node holds the timeout in seconds after which the
TOYMATCH2 event should wake the system.
The 'wakesrc' node holds the SYS_WAKESRC register after wakeup (in hex),
the 'wakemsk' node can be used to get/set the wakeup mask directly.
For example, to have the timer wake the system after 10 seconds of sleep,
the following must be done in userspace:
echo 10 > /sys/power/db1x/timer_timeout
echo 1 > /sys/power/db1x/timer
echo mem > /sys/power/sleep
This patch also removes the homebrew CPU frequency switching code. I don't
understand how it could have ever worked reliably; it does not communicate
the clock changes to peripheral devices other than uarts.
Signed-off-by: Manuel Lauss <mano@roarinelk.homelinux.net>
Signed-off-by: Ralf Baechle <ralf@linux-mips.org>
create mode 100644 arch/mips/alchemy/devboards/pm.c
Diffstat (limited to 'arch/mips/alchemy')
-rw-r--r-- | arch/mips/alchemy/common/irq.c | 44 | ||||
-rw-r--r-- | arch/mips/alchemy/common/power.c | 309 | ||||
-rw-r--r-- | arch/mips/alchemy/devboards/Makefile | 1 | ||||
-rw-r--r-- | arch/mips/alchemy/devboards/pm.c | 229 |
4 files changed, 230 insertions, 353 deletions
diff --git a/arch/mips/alchemy/common/irq.c b/arch/mips/alchemy/common/irq.c index c54384779fb9..c88c821b4c36 100644 --- a/arch/mips/alchemy/common/irq.c +++ b/arch/mips/alchemy/common/irq.c | |||
@@ -37,8 +37,6 @@ | |||
37 | #include <asm/mach-pb1x00/pb1000.h> | 37 | #include <asm/mach-pb1x00/pb1000.h> |
38 | #endif | 38 | #endif |
39 | 39 | ||
40 | static DEFINE_SPINLOCK(irq_lock); | ||
41 | |||
42 | static int au1x_ic_settype(unsigned int irq, unsigned int flow_type); | 40 | static int au1x_ic_settype(unsigned int irq, unsigned int flow_type); |
43 | 41 | ||
44 | /* per-processor fixed function irqs */ | 42 | /* per-processor fixed function irqs */ |
@@ -611,45 +609,3 @@ void __init arch_init_irq(void) | |||
611 | 609 | ||
612 | set_c0_status(IE_IRQ0 | IE_IRQ1 | IE_IRQ2 | IE_IRQ3); | 610 | set_c0_status(IE_IRQ0 | IE_IRQ1 | IE_IRQ2 | IE_IRQ3); |
613 | } | 611 | } |
614 | |||
615 | unsigned long save_local_and_disable(int controller) | ||
616 | { | ||
617 | int i; | ||
618 | unsigned long flags, mask; | ||
619 | |||
620 | spin_lock_irqsave(&irq_lock, flags); | ||
621 | if (controller) { | ||
622 | mask = au_readl(IC1_MASKSET); | ||
623 | for (i = 0; i < 32; i++) | ||
624 | au1x_ic1_mask(i + AU1000_INTC1_INT_BASE); | ||
625 | } else { | ||
626 | mask = au_readl(IC0_MASKSET); | ||
627 | for (i = 0; i < 32; i++) | ||
628 | au1x_ic0_mask(i + AU1000_INTC0_INT_BASE); | ||
629 | } | ||
630 | spin_unlock_irqrestore(&irq_lock, flags); | ||
631 | |||
632 | return mask; | ||
633 | } | ||
634 | |||
635 | void restore_local_and_enable(int controller, unsigned long mask) | ||
636 | { | ||
637 | int i; | ||
638 | unsigned long flags, new_mask; | ||
639 | |||
640 | spin_lock_irqsave(&irq_lock, flags); | ||
641 | for (i = 0; i < 32; i++) | ||
642 | if (mask & (1 << i)) { | ||
643 | if (controller) | ||
644 | au1x_ic1_unmask(i + AU1000_INTC1_INT_BASE); | ||
645 | else | ||
646 | au1x_ic0_unmask(i + AU1000_INTC0_INT_BASE); | ||
647 | } | ||
648 | |||
649 | if (controller) | ||
650 | new_mask = au_readl(IC1_MASKSET); | ||
651 | else | ||
652 | new_mask = au_readl(IC0_MASKSET); | ||
653 | |||
654 | spin_unlock_irqrestore(&irq_lock, flags); | ||
655 | } | ||
diff --git a/arch/mips/alchemy/common/power.c b/arch/mips/alchemy/common/power.c index f58e151b38d4..6ab7b42aa1be 100644 --- a/arch/mips/alchemy/common/power.c +++ b/arch/mips/alchemy/common/power.c | |||
@@ -42,18 +42,6 @@ | |||
42 | 42 | ||
43 | #ifdef CONFIG_PM | 43 | #ifdef CONFIG_PM |
44 | 44 | ||
45 | #define DEBUG 1 | ||
46 | #ifdef DEBUG | ||
47 | #define DPRINTK(fmt, args...) printk(KERN_DEBUG "%s: " fmt, __func__, ## args) | ||
48 | #else | ||
49 | #define DPRINTK(fmt, args...) | ||
50 | #endif | ||
51 | |||
52 | extern unsigned long save_local_and_disable(int controller); | ||
53 | extern void restore_local_and_enable(int controller, unsigned long mask); | ||
54 | |||
55 | static DEFINE_SPINLOCK(pm_lock); | ||
56 | |||
57 | /* | 45 | /* |
58 | * We need to save/restore a bunch of core registers that are | 46 | * We need to save/restore a bunch of core registers that are |
59 | * either volatile or reset to some state across a processor sleep. | 47 | * either volatile or reset to some state across a processor sleep. |
@@ -74,21 +62,6 @@ static unsigned int sleep_sys_clocks[5]; | |||
74 | static unsigned int sleep_sys_pinfunc; | 62 | static unsigned int sleep_sys_pinfunc; |
75 | static unsigned int sleep_static_memctlr[4][3]; | 63 | static unsigned int sleep_static_memctlr[4][3]; |
76 | 64 | ||
77 | /* | ||
78 | * Define this to cause the value you write to /proc/sys/pm/sleep to | ||
79 | * set the TOY timer for the amount of time you want to sleep. | ||
80 | * This is done mainly for testing, but may be useful in other cases. | ||
81 | * The value is number of 32KHz ticks to sleep. | ||
82 | */ | ||
83 | #define SLEEP_TEST_TIMEOUT 1 | ||
84 | #ifdef SLEEP_TEST_TIMEOUT | ||
85 | static int sleep_ticks; | ||
86 | static void wakeup_counter0_set(int ticks) | ||
87 | { | ||
88 | au_writel(au_readl(SYS_TOYREAD) + ticks, SYS_TOYMATCH2); | ||
89 | au_sync(); | ||
90 | } | ||
91 | #endif | ||
92 | 65 | ||
93 | static void save_core_regs(void) | 66 | static void save_core_regs(void) |
94 | { | 67 | { |
@@ -234,13 +207,6 @@ static void restore_core_regs(void) | |||
234 | #endif | 207 | #endif |
235 | } | 208 | } |
236 | 209 | ||
237 | unsigned long suspend_mode; | ||
238 | |||
239 | void wakeup_from_suspend(void) | ||
240 | { | ||
241 | suspend_mode = 0; | ||
242 | } | ||
243 | |||
244 | void au_sleep(void) | 210 | void au_sleep(void) |
245 | { | 211 | { |
246 | save_core_regs(); | 212 | save_core_regs(); |
@@ -248,279 +214,4 @@ void au_sleep(void) | |||
248 | restore_core_regs(); | 214 | restore_core_regs(); |
249 | } | 215 | } |
250 | 216 | ||
251 | static int pm_do_sleep(ctl_table *ctl, int write, struct file *file, | ||
252 | void __user *buffer, size_t *len, loff_t *ppos) | ||
253 | { | ||
254 | unsigned long wakeup, flags; | ||
255 | int ret; | ||
256 | #ifdef SLEEP_TEST_TIMEOUT | ||
257 | #define TMPBUFLEN2 16 | ||
258 | char buf[TMPBUFLEN2], *p; | ||
259 | #endif | ||
260 | |||
261 | spin_lock_irqsave(&pm_lock, flags); | ||
262 | |||
263 | if (!write) { | ||
264 | *len = 0; | ||
265 | ret = 0; | ||
266 | goto out_unlock; | ||
267 | }; | ||
268 | |||
269 | #ifdef SLEEP_TEST_TIMEOUT | ||
270 | if (*len > TMPBUFLEN2 - 1) { | ||
271 | ret = -EFAULT; | ||
272 | goto out_unlock; | ||
273 | } | ||
274 | if (copy_from_user(buf, buffer, *len)) { | ||
275 | return -EFAULT; | ||
276 | goto out_unlock; | ||
277 | } | ||
278 | buf[*len] = 0; | ||
279 | p = buf; | ||
280 | sleep_ticks = simple_strtoul(p, &p, 0); | ||
281 | wakeup_counter0_set(sleep_ticks); | ||
282 | #endif | ||
283 | |||
284 | /** | ||
285 | ** The code below is all system dependent and we should probably | ||
286 | ** have a function call out of here to set this up. You need | ||
287 | ** to configure the GPIO or timer interrupts that will bring | ||
288 | ** you out of sleep. | ||
289 | ** For testing, the TOY counter wakeup is useful. | ||
290 | **/ | ||
291 | #if 0 | ||
292 | au_writel(au_readl(SYS_PINSTATERD) & ~(1 << 11), SYS_PINSTATERD); | ||
293 | |||
294 | /* GPIO 6 can cause a wake up event */ | ||
295 | wakeup = au_readl(SYS_WAKEMSK); | ||
296 | wakeup &= ~(1 << 8); /* turn off match20 wakeup */ | ||
297 | wakeup |= 1 << 6; /* turn on GPIO 6 wakeup */ | ||
298 | #else | ||
299 | /* For testing, allow match20 to wake us up. */ | ||
300 | wakeup = 1 << 8; /* turn on match20 wakeup */ | ||
301 | wakeup = 0; | ||
302 | #endif | ||
303 | au_writel(1, SYS_WAKESRC); /* clear cause */ | ||
304 | au_sync(); | ||
305 | au_writel(wakeup, SYS_WAKEMSK); | ||
306 | au_sync(); | ||
307 | |||
308 | au_sleep(); | ||
309 | ret = 0; | ||
310 | |||
311 | out_unlock: | ||
312 | spin_unlock_irqrestore(&pm_lock, flags); | ||
313 | return ret; | ||
314 | } | ||
315 | |||
316 | #if !defined(CONFIG_SOC_AU1200) && !defined(CONFIG_SOC_AU1550) | ||
317 | |||
318 | /* | ||
319 | * This is right out of init/main.c | ||
320 | */ | ||
321 | |||
322 | /* | ||
323 | * This is the number of bits of precision for the loops_per_jiffy. | ||
324 | * Each bit takes on average 1.5/HZ seconds. This (like the original) | ||
325 | * is a little better than 1%. | ||
326 | */ | ||
327 | #define LPS_PREC 8 | ||
328 | |||
329 | static void au1000_calibrate_delay(void) | ||
330 | { | ||
331 | unsigned long ticks, loopbit; | ||
332 | int lps_precision = LPS_PREC; | ||
333 | |||
334 | loops_per_jiffy = 1 << 12; | ||
335 | |||
336 | while (loops_per_jiffy <<= 1) { | ||
337 | /* Wait for "start of" clock tick */ | ||
338 | ticks = jiffies; | ||
339 | while (ticks == jiffies) | ||
340 | /* nothing */ ; | ||
341 | /* Go ... */ | ||
342 | ticks = jiffies; | ||
343 | __delay(loops_per_jiffy); | ||
344 | ticks = jiffies - ticks; | ||
345 | if (ticks) | ||
346 | break; | ||
347 | } | ||
348 | |||
349 | /* | ||
350 | * Do a binary approximation to get loops_per_jiffy set to be equal | ||
351 | * one clock (up to lps_precision bits) | ||
352 | */ | ||
353 | loops_per_jiffy >>= 1; | ||
354 | loopbit = loops_per_jiffy; | ||
355 | while (lps_precision-- && (loopbit >>= 1)) { | ||
356 | loops_per_jiffy |= loopbit; | ||
357 | ticks = jiffies; | ||
358 | while (ticks == jiffies); | ||
359 | ticks = jiffies; | ||
360 | __delay(loops_per_jiffy); | ||
361 | if (jiffies != ticks) /* longer than 1 tick */ | ||
362 | loops_per_jiffy &= ~loopbit; | ||
363 | } | ||
364 | } | ||
365 | |||
366 | static int pm_do_freq(ctl_table *ctl, int write, struct file *file, | ||
367 | void __user *buffer, size_t *len, loff_t *ppos) | ||
368 | { | ||
369 | int retval = 0, i; | ||
370 | unsigned long val, pll; | ||
371 | #define TMPBUFLEN 64 | ||
372 | #define MAX_CPU_FREQ 396 | ||
373 | char buf[TMPBUFLEN], *p; | ||
374 | unsigned long flags, intc0_mask, intc1_mask; | ||
375 | unsigned long old_baud_base, old_cpu_freq, old_clk, old_refresh; | ||
376 | unsigned long new_baud_base, new_cpu_freq, new_clk, new_refresh; | ||
377 | unsigned long baud_rate; | ||
378 | |||
379 | spin_lock_irqsave(&pm_lock, flags); | ||
380 | if (!write) | ||
381 | *len = 0; | ||
382 | else { | ||
383 | /* Parse the new frequency */ | ||
384 | if (*len > TMPBUFLEN - 1) { | ||
385 | spin_unlock_irqrestore(&pm_lock, flags); | ||
386 | return -EFAULT; | ||
387 | } | ||
388 | if (copy_from_user(buf, buffer, *len)) { | ||
389 | spin_unlock_irqrestore(&pm_lock, flags); | ||
390 | return -EFAULT; | ||
391 | } | ||
392 | buf[*len] = 0; | ||
393 | p = buf; | ||
394 | val = simple_strtoul(p, &p, 0); | ||
395 | if (val > MAX_CPU_FREQ) { | ||
396 | spin_unlock_irqrestore(&pm_lock, flags); | ||
397 | return -EFAULT; | ||
398 | } | ||
399 | |||
400 | pll = val / 12; | ||
401 | if ((pll > 33) || (pll < 7)) { /* 396 MHz max, 84 MHz min */ | ||
402 | /* Revisit this for higher speed CPUs */ | ||
403 | spin_unlock_irqrestore(&pm_lock, flags); | ||
404 | return -EFAULT; | ||
405 | } | ||
406 | |||
407 | old_baud_base = get_au1x00_uart_baud_base(); | ||
408 | old_cpu_freq = get_au1x00_speed(); | ||
409 | |||
410 | new_cpu_freq = pll * 12 * 1000000; | ||
411 | new_baud_base = (new_cpu_freq / (2 * ((int)(au_readl(SYS_POWERCTRL) | ||
412 | & 0x03) + 2) * 16)); | ||
413 | set_au1x00_speed(new_cpu_freq); | ||
414 | set_au1x00_uart_baud_base(new_baud_base); | ||
415 | |||
416 | old_refresh = au_readl(MEM_SDREFCFG) & 0x1ffffff; | ||
417 | new_refresh = ((old_refresh * new_cpu_freq) / old_cpu_freq) | | ||
418 | (au_readl(MEM_SDREFCFG) & ~0x1ffffff); | ||
419 | |||
420 | au_writel(pll, SYS_CPUPLL); | ||
421 | au_sync_delay(1); | ||
422 | au_writel(new_refresh, MEM_SDREFCFG); | ||
423 | au_sync_delay(1); | ||
424 | |||
425 | for (i = 0; i < 4; i++) | ||
426 | if (au_readl(UART_BASE + UART_MOD_CNTRL + | ||
427 | i * 0x00100000) == 3) { | ||
428 | old_clk = au_readl(UART_BASE + UART_CLK + | ||
429 | i * 0x00100000); | ||
430 | baud_rate = old_baud_base / old_clk; | ||
431 | /* | ||
432 | * We won't get an exact baud rate and the error | ||
433 | * could be significant enough that our new | ||
434 | * calculation will result in a clock that will | ||
435 | * give us a baud rate that's too far off from | ||
436 | * what we really want. | ||
437 | */ | ||
438 | if (baud_rate > 100000) | ||
439 | baud_rate = 115200; | ||
440 | else if (baud_rate > 50000) | ||
441 | baud_rate = 57600; | ||
442 | else if (baud_rate > 30000) | ||
443 | baud_rate = 38400; | ||
444 | else if (baud_rate > 17000) | ||
445 | baud_rate = 19200; | ||
446 | else | ||
447 | baud_rate = 9600; | ||
448 | new_clk = new_baud_base / baud_rate; | ||
449 | au_writel(new_clk, UART_BASE + UART_CLK + | ||
450 | i * 0x00100000); | ||
451 | au_sync_delay(10); | ||
452 | } | ||
453 | } | ||
454 | |||
455 | /* | ||
456 | * We don't want _any_ interrupts other than match20. Otherwise our | ||
457 | * au1000_calibrate_delay() calculation will be off, potentially a lot. | ||
458 | */ | ||
459 | intc0_mask = save_local_and_disable(0); | ||
460 | intc1_mask = save_local_and_disable(1); | ||
461 | val = 1 << (AU1000_TOY_MATCH2_INT - AU1000_INTC0_INT_BASE); | ||
462 | au_writel(val, IC0_MASKSET); /* unmask */ | ||
463 | au_writel(val, IC0_WAKESET); /* enable wake-from-sleep */ | ||
464 | au_sync(); | ||
465 | spin_unlock_irqrestore(&pm_lock, flags); | ||
466 | au1000_calibrate_delay(); | ||
467 | restore_local_and_enable(0, intc0_mask); | ||
468 | restore_local_and_enable(1, intc1_mask); | ||
469 | |||
470 | return retval; | ||
471 | } | ||
472 | #endif | ||
473 | |||
474 | static struct ctl_table pm_table[] = { | ||
475 | { | ||
476 | .ctl_name = CTL_UNNUMBERED, | ||
477 | .procname = "sleep", | ||
478 | .data = NULL, | ||
479 | .maxlen = 0, | ||
480 | .mode = 0600, | ||
481 | .proc_handler = &pm_do_sleep | ||
482 | }, | ||
483 | #if !defined(CONFIG_SOC_AU1200) && !defined(CONFIG_SOC_AU1550) | ||
484 | { | ||
485 | .ctl_name = CTL_UNNUMBERED, | ||
486 | .procname = "freq", | ||
487 | .data = NULL, | ||
488 | .maxlen = 0, | ||
489 | .mode = 0600, | ||
490 | .proc_handler = &pm_do_freq | ||
491 | }, | ||
492 | #endif | ||
493 | {} | ||
494 | }; | ||
495 | |||
496 | static struct ctl_table pm_dir_table[] = { | ||
497 | { | ||
498 | .ctl_name = CTL_UNNUMBERED, | ||
499 | .procname = "pm", | ||
500 | .mode = 0555, | ||
501 | .child = pm_table | ||
502 | }, | ||
503 | {} | ||
504 | }; | ||
505 | |||
506 | /* | ||
507 | * Initialize power interface | ||
508 | */ | ||
509 | static int __init pm_init(void) | ||
510 | { | ||
511 | /* init TOY to tick at 1Hz. No need to wait for access bits | ||
512 | * since there's plenty of time between here and the first | ||
513 | * suspend cycle. | ||
514 | */ | ||
515 | if (au_readl(SYS_TOYTRIM) != 32767) { | ||
516 | au_writel(32767, SYS_TOYTRIM); | ||
517 | au_sync(); | ||
518 | } | ||
519 | |||
520 | register_sysctl_table(pm_dir_table); | ||
521 | return 0; | ||
522 | } | ||
523 | |||
524 | __initcall(pm_init); | ||
525 | |||
526 | #endif /* CONFIG_PM */ | 217 | #endif /* CONFIG_PM */ |
diff --git a/arch/mips/alchemy/devboards/Makefile b/arch/mips/alchemy/devboards/Makefile index c0eb87a66fbd..730f9f2b30e8 100644 --- a/arch/mips/alchemy/devboards/Makefile +++ b/arch/mips/alchemy/devboards/Makefile | |||
@@ -3,6 +3,7 @@ | |||
3 | # | 3 | # |
4 | 4 | ||
5 | obj-y += prom.o | 5 | obj-y += prom.o |
6 | obj-$(CONFIG_PM) += pm.o | ||
6 | obj-$(CONFIG_MIPS_PB1000) += pb1000/ | 7 | obj-$(CONFIG_MIPS_PB1000) += pb1000/ |
7 | obj-$(CONFIG_MIPS_PB1100) += pb1100/ | 8 | obj-$(CONFIG_MIPS_PB1100) += pb1100/ |
8 | obj-$(CONFIG_MIPS_PB1200) += pb1200/ | 9 | obj-$(CONFIG_MIPS_PB1200) += pb1200/ |
diff --git a/arch/mips/alchemy/devboards/pm.c b/arch/mips/alchemy/devboards/pm.c new file mode 100644 index 000000000000..d5eb9c325ed0 --- /dev/null +++ b/arch/mips/alchemy/devboards/pm.c | |||
@@ -0,0 +1,229 @@ | |||
1 | /* | ||
2 | * Alchemy Development Board example suspend userspace interface. | ||
3 | * | ||
4 | * (c) 2008 Manuel Lauss <mano@roarinelk.homelinux.net> | ||
5 | */ | ||
6 | |||
7 | #include <linux/init.h> | ||
8 | #include <linux/kobject.h> | ||
9 | #include <linux/suspend.h> | ||
10 | #include <linux/sysfs.h> | ||
11 | #include <asm/mach-au1x00/au1000.h> | ||
12 | |||
13 | /* | ||
14 | * Generic suspend userspace interface for Alchemy development boards. | ||
15 | * This code exports a few sysfs nodes under /sys/power/db1x/ which | ||
16 | * can be used by userspace to en/disable all au1x-provided wakeup | ||
17 | * sources and configure the timeout after which the the TOYMATCH2 irq | ||
18 | * is to trigger a wakeup. | ||
19 | */ | ||
20 | |||
21 | |||
22 | static unsigned long db1x_pm_sleep_secs; | ||
23 | static unsigned long db1x_pm_wakemsk; | ||
24 | static unsigned long db1x_pm_last_wakesrc; | ||
25 | |||
26 | static int db1x_pm_enter(suspend_state_t state) | ||
27 | { | ||
28 | /* enable GPIO based wakeup */ | ||
29 | au_writel(1, SYS_PININPUTEN); | ||
30 | |||
31 | /* clear and setup wake cause and source */ | ||
32 | au_writel(0, SYS_WAKEMSK); | ||
33 | au_sync(); | ||
34 | au_writel(0, SYS_WAKESRC); | ||
35 | au_sync(); | ||
36 | |||
37 | au_writel(db1x_pm_wakemsk, SYS_WAKEMSK); | ||
38 | au_sync(); | ||
39 | |||
40 | /* setup 1Hz-timer-based wakeup: wait for reg access */ | ||
41 | while (au_readl(SYS_COUNTER_CNTRL) & SYS_CNTRL_M20) | ||
42 | asm volatile ("nop"); | ||
43 | |||
44 | au_writel(au_readl(SYS_TOYREAD) + db1x_pm_sleep_secs, SYS_TOYMATCH2); | ||
45 | au_sync(); | ||
46 | |||
47 | /* wait for value to really hit the register */ | ||
48 | while (au_readl(SYS_COUNTER_CNTRL) & SYS_CNTRL_M20) | ||
49 | asm volatile ("nop"); | ||
50 | |||
51 | /* ...and now the sandman can come! */ | ||
52 | au_sleep(); | ||
53 | |||
54 | return 0; | ||
55 | } | ||
56 | |||
57 | static int db1x_pm_begin(suspend_state_t state) | ||
58 | { | ||
59 | if (!db1x_pm_wakemsk) { | ||
60 | printk(KERN_ERR "db1x: no wakeup source activated!\n"); | ||
61 | return -EINVAL; | ||
62 | } | ||
63 | |||
64 | return 0; | ||
65 | } | ||
66 | |||
67 | static void db1x_pm_end(void) | ||
68 | { | ||
69 | /* read and store wakeup source, the clear the register. To | ||
70 | * be able to clear it, WAKEMSK must be cleared first. | ||
71 | */ | ||
72 | db1x_pm_last_wakesrc = au_readl(SYS_WAKESRC); | ||
73 | |||
74 | au_writel(0, SYS_WAKEMSK); | ||
75 | au_writel(0, SYS_WAKESRC); | ||
76 | au_sync(); | ||
77 | |||
78 | } | ||
79 | |||
80 | static struct platform_suspend_ops db1x_pm_ops = { | ||
81 | .valid = suspend_valid_only_mem, | ||
82 | .begin = db1x_pm_begin, | ||
83 | .enter = db1x_pm_enter, | ||
84 | .end = db1x_pm_end, | ||
85 | }; | ||
86 | |||
87 | #define ATTRCMP(x) (0 == strcmp(attr->attr.name, #x)) | ||
88 | |||
89 | static ssize_t db1x_pmattr_show(struct kobject *kobj, | ||
90 | struct kobj_attribute *attr, | ||
91 | char *buf) | ||
92 | { | ||
93 | int idx; | ||
94 | |||
95 | if (ATTRCMP(timer_timeout)) | ||
96 | return sprintf(buf, "%lu\n", db1x_pm_sleep_secs); | ||
97 | |||
98 | else if (ATTRCMP(timer)) | ||
99 | return sprintf(buf, "%u\n", | ||
100 | !!(db1x_pm_wakemsk & SYS_WAKEMSK_M2)); | ||
101 | |||
102 | else if (ATTRCMP(wakesrc)) | ||
103 | return sprintf(buf, "%lu\n", db1x_pm_last_wakesrc); | ||
104 | |||
105 | else if (ATTRCMP(gpio0) || ATTRCMP(gpio1) || ATTRCMP(gpio2) || | ||
106 | ATTRCMP(gpio3) || ATTRCMP(gpio4) || ATTRCMP(gpio5) || | ||
107 | ATTRCMP(gpio6) || ATTRCMP(gpio7)) { | ||
108 | idx = (attr->attr.name)[4] - '0'; | ||
109 | return sprintf(buf, "%d\n", | ||
110 | !!(db1x_pm_wakemsk & SYS_WAKEMSK_GPIO(idx))); | ||
111 | |||
112 | } else if (ATTRCMP(wakemsk)) { | ||
113 | return sprintf(buf, "%08lx\n", db1x_pm_wakemsk); | ||
114 | } | ||
115 | |||
116 | return -ENOENT; | ||
117 | } | ||
118 | |||
119 | static ssize_t db1x_pmattr_store(struct kobject *kobj, | ||
120 | struct kobj_attribute *attr, | ||
121 | const char *instr, | ||
122 | size_t bytes) | ||
123 | { | ||
124 | unsigned long l; | ||
125 | int tmp; | ||
126 | |||
127 | if (ATTRCMP(timer_timeout)) { | ||
128 | tmp = strict_strtoul(instr, 0, &l); | ||
129 | if (tmp) | ||
130 | return tmp; | ||
131 | |||
132 | db1x_pm_sleep_secs = l; | ||
133 | |||
134 | } else if (ATTRCMP(timer)) { | ||
135 | if (instr[0] != '0') | ||
136 | db1x_pm_wakemsk |= SYS_WAKEMSK_M2; | ||
137 | else | ||
138 | db1x_pm_wakemsk &= ~SYS_WAKEMSK_M2; | ||
139 | |||
140 | } else if (ATTRCMP(gpio0) || ATTRCMP(gpio1) || ATTRCMP(gpio2) || | ||
141 | ATTRCMP(gpio3) || ATTRCMP(gpio4) || ATTRCMP(gpio5) || | ||
142 | ATTRCMP(gpio6) || ATTRCMP(gpio7)) { | ||
143 | tmp = (attr->attr.name)[4] - '0'; | ||
144 | if (instr[0] != '0') { | ||
145 | db1x_pm_wakemsk |= SYS_WAKEMSK_GPIO(tmp); | ||
146 | } else { | ||
147 | db1x_pm_wakemsk &= ~SYS_WAKEMSK_GPIO(tmp); | ||
148 | } | ||
149 | |||
150 | } else if (ATTRCMP(wakemsk)) { | ||
151 | tmp = strict_strtoul(instr, 0, &l); | ||
152 | if (tmp) | ||
153 | return tmp; | ||
154 | |||
155 | db1x_pm_wakemsk = l & 0x0000003f; | ||
156 | |||
157 | } else | ||
158 | bytes = -ENOENT; | ||
159 | |||
160 | return bytes; | ||
161 | } | ||
162 | |||
163 | #define ATTR(x) \ | ||
164 | static struct kobj_attribute x##_attribute = \ | ||
165 | __ATTR(x, 0664, db1x_pmattr_show, \ | ||
166 | db1x_pmattr_store); | ||
167 | |||
168 | ATTR(gpio0) /* GPIO-based wakeup enable */ | ||
169 | ATTR(gpio1) | ||
170 | ATTR(gpio2) | ||
171 | ATTR(gpio3) | ||
172 | ATTR(gpio4) | ||
173 | ATTR(gpio5) | ||
174 | ATTR(gpio6) | ||
175 | ATTR(gpio7) | ||
176 | ATTR(timer) /* TOYMATCH2-based wakeup enable */ | ||
177 | ATTR(timer_timeout) /* timer-based wakeup timeout value, in seconds */ | ||
178 | ATTR(wakesrc) /* contents of SYS_WAKESRC after last wakeup */ | ||
179 | ATTR(wakemsk) /* direct access to SYS_WAKEMSK */ | ||
180 | |||
181 | #define ATTR_LIST(x) & x ## _attribute.attr | ||
182 | static struct attribute *db1x_pmattrs[] = { | ||
183 | ATTR_LIST(gpio0), | ||
184 | ATTR_LIST(gpio1), | ||
185 | ATTR_LIST(gpio2), | ||
186 | ATTR_LIST(gpio3), | ||
187 | ATTR_LIST(gpio4), | ||
188 | ATTR_LIST(gpio5), | ||
189 | ATTR_LIST(gpio6), | ||
190 | ATTR_LIST(gpio7), | ||
191 | ATTR_LIST(timer), | ||
192 | ATTR_LIST(timer_timeout), | ||
193 | ATTR_LIST(wakesrc), | ||
194 | ATTR_LIST(wakemsk), | ||
195 | NULL, /* terminator */ | ||
196 | }; | ||
197 | |||
198 | static struct attribute_group db1x_pmattr_group = { | ||
199 | .name = "db1x", | ||
200 | .attrs = db1x_pmattrs, | ||
201 | }; | ||
202 | |||
203 | /* | ||
204 | * Initialize suspend interface | ||
205 | */ | ||
206 | static int __init pm_init(void) | ||
207 | { | ||
208 | /* init TOY to tick at 1Hz if not already done. No need to wait | ||
209 | * for confirmation since there's plenty of time from here to | ||
210 | * the next suspend cycle. | ||
211 | */ | ||
212 | if (au_readl(SYS_TOYTRIM) != 32767) { | ||
213 | au_writel(32767, SYS_TOYTRIM); | ||
214 | au_sync(); | ||
215 | } | ||
216 | |||
217 | db1x_pm_last_wakesrc = au_readl(SYS_WAKESRC); | ||
218 | |||
219 | au_writel(0, SYS_WAKESRC); | ||
220 | au_sync(); | ||
221 | au_writel(0, SYS_WAKEMSK); | ||
222 | au_sync(); | ||
223 | |||
224 | suspend_set_ops(&db1x_pm_ops); | ||
225 | |||
226 | return sysfs_create_group(power_kobj, &db1x_pmattr_group); | ||
227 | } | ||
228 | |||
229 | late_initcall(pm_init); | ||