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 | |
| 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
| -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 | ||||
| -rw-r--r-- | arch/mips/include/asm/mach-au1x00/au1000.h | 4 |
5 files changed, 234 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); | ||
diff --git a/arch/mips/include/asm/mach-au1x00/au1000.h b/arch/mips/include/asm/mach-au1x00/au1000.h index 515373c6d4a1..62f91f50b5b5 100644 --- a/arch/mips/include/asm/mach-au1x00/au1000.h +++ b/arch/mips/include/asm/mach-au1x00/au1000.h | |||
| @@ -1560,6 +1560,10 @@ enum soc_au1200_ints { | |||
| 1560 | #define SYS_SLPPWR 0xB1900078 | 1560 | #define SYS_SLPPWR 0xB1900078 |
| 1561 | #define SYS_SLEEP 0xB190007C | 1561 | #define SYS_SLEEP 0xB190007C |
| 1562 | 1562 | ||
| 1563 | #define SYS_WAKEMSK_D2 (1 << 9) | ||
| 1564 | #define SYS_WAKEMSK_M2 (1 << 8) | ||
| 1565 | #define SYS_WAKEMSK_GPIO(x) (1 << (x)) | ||
| 1566 | |||
| 1563 | /* Clock Controller */ | 1567 | /* Clock Controller */ |
| 1564 | #define SYS_FREQCTRL0 0xB1900020 | 1568 | #define SYS_FREQCTRL0 0xB1900020 |
| 1565 | # define SYS_FC_FRDIV2_BIT 22 | 1569 | # define SYS_FC_FRDIV2_BIT 22 |
