diff options
Diffstat (limited to 'arch/arm/plat-s3c24xx/pm.c')
| -rw-r--r-- | arch/arm/plat-s3c24xx/pm.c | 247 |
1 files changed, 214 insertions, 33 deletions
diff --git a/arch/arm/plat-s3c24xx/pm.c b/arch/arm/plat-s3c24xx/pm.c index 4fdb3117744f..bf5581a9aeea 100644 --- a/arch/arm/plat-s3c24xx/pm.c +++ b/arch/arm/plat-s3c24xx/pm.c | |||
| @@ -83,38 +83,39 @@ static struct sleep_save core_save[] = { | |||
| 83 | SAVE_ITEM(S3C2410_REFRESH), | 83 | SAVE_ITEM(S3C2410_REFRESH), |
| 84 | }; | 84 | }; |
| 85 | 85 | ||
| 86 | static struct sleep_save gpio_save[] = { | 86 | static struct gpio_sleep { |
| 87 | SAVE_ITEM(S3C2410_GPACON), | 87 | void __iomem *base; |
| 88 | SAVE_ITEM(S3C2410_GPADAT), | 88 | unsigned int gpcon; |
| 89 | 89 | unsigned int gpdat; | |
| 90 | SAVE_ITEM(S3C2410_GPBCON), | 90 | unsigned int gpup; |
| 91 | SAVE_ITEM(S3C2410_GPBDAT), | 91 | } gpio_save[] = { |
| 92 | SAVE_ITEM(S3C2410_GPBUP), | 92 | [0] = { |
| 93 | 93 | .base = S3C2410_GPACON, | |
| 94 | SAVE_ITEM(S3C2410_GPCCON), | 94 | }, |
| 95 | SAVE_ITEM(S3C2410_GPCDAT), | 95 | [1] = { |
| 96 | SAVE_ITEM(S3C2410_GPCUP), | 96 | .base = S3C2410_GPBCON, |
| 97 | 97 | }, | |
| 98 | SAVE_ITEM(S3C2410_GPDCON), | 98 | [2] = { |
| 99 | SAVE_ITEM(S3C2410_GPDDAT), | 99 | .base = S3C2410_GPCCON, |
| 100 | SAVE_ITEM(S3C2410_GPDUP), | 100 | }, |
| 101 | 101 | [3] = { | |
| 102 | SAVE_ITEM(S3C2410_GPECON), | 102 | .base = S3C2410_GPDCON, |
| 103 | SAVE_ITEM(S3C2410_GPEDAT), | 103 | }, |
| 104 | SAVE_ITEM(S3C2410_GPEUP), | 104 | [4] = { |
| 105 | 105 | .base = S3C2410_GPECON, | |
| 106 | SAVE_ITEM(S3C2410_GPFCON), | 106 | }, |
| 107 | SAVE_ITEM(S3C2410_GPFDAT), | 107 | [5] = { |
| 108 | SAVE_ITEM(S3C2410_GPFUP), | 108 | .base = S3C2410_GPFCON, |
| 109 | 109 | }, | |
| 110 | SAVE_ITEM(S3C2410_GPGCON), | 110 | [6] = { |
| 111 | SAVE_ITEM(S3C2410_GPGDAT), | 111 | .base = S3C2410_GPGCON, |
| 112 | SAVE_ITEM(S3C2410_GPGUP), | 112 | }, |
| 113 | 113 | [7] = { | |
| 114 | SAVE_ITEM(S3C2410_GPHCON), | 114 | .base = S3C2410_GPHCON, |
| 115 | SAVE_ITEM(S3C2410_GPHDAT), | 115 | }, |
| 116 | SAVE_ITEM(S3C2410_GPHUP), | 116 | }; |
| 117 | 117 | ||
| 118 | static struct sleep_save misc_save[] = { | ||
| 118 | SAVE_ITEM(S3C2410_DCLKCON), | 119 | SAVE_ITEM(S3C2410_DCLKCON), |
| 119 | }; | 120 | }; |
| 120 | 121 | ||
| @@ -486,6 +487,184 @@ static void s3c2410_pm_configure_extint(void) | |||
| 486 | } | 487 | } |
| 487 | } | 488 | } |
| 488 | 489 | ||
| 490 | /* offsets for CON/DAT/UP registers */ | ||
| 491 | |||
| 492 | #define OFFS_CON (S3C2410_GPACON - S3C2410_GPACON) | ||
| 493 | #define OFFS_DAT (S3C2410_GPADAT - S3C2410_GPACON) | ||
| 494 | #define OFFS_UP (S3C2410_GPBUP - S3C2410_GPBCON) | ||
| 495 | |||
| 496 | /* s3c2410_pm_save_gpios() | ||
| 497 | * | ||
| 498 | * Save the state of the GPIOs | ||
| 499 | */ | ||
| 500 | |||
| 501 | static void s3c2410_pm_save_gpios(void) | ||
| 502 | { | ||
| 503 | struct gpio_sleep *gps = gpio_save; | ||
| 504 | unsigned int gpio; | ||
| 505 | |||
| 506 | for (gpio = 0; gpio < ARRAY_SIZE(gpio_save); gpio++, gps++) { | ||
| 507 | void __iomem *base = gps->base; | ||
| 508 | |||
| 509 | gps->gpcon = __raw_readl(base + OFFS_CON); | ||
| 510 | gps->gpdat = __raw_readl(base + OFFS_DAT); | ||
| 511 | |||
| 512 | if (gpio > 0) | ||
| 513 | gps->gpup = __raw_readl(base + OFFS_UP); | ||
| 514 | |||
| 515 | } | ||
| 516 | } | ||
| 517 | |||
| 518 | /* Test whether the given masked+shifted bits of an GPIO configuration | ||
| 519 | * are one of the SFN (special function) modes. */ | ||
| 520 | |||
| 521 | static inline int is_sfn(unsigned long con) | ||
| 522 | { | ||
| 523 | return (con == 2 || con == 3); | ||
| 524 | } | ||
| 525 | |||
| 526 | /* Test if the given masked+shifted GPIO configuration is an input */ | ||
| 527 | |||
| 528 | static inline int is_in(unsigned long con) | ||
| 529 | { | ||
| 530 | return con == 0; | ||
| 531 | } | ||
| 532 | |||
| 533 | /* Test if the given masked+shifted GPIO configuration is an output */ | ||
| 534 | |||
| 535 | static inline int is_out(unsigned long con) | ||
| 536 | { | ||
| 537 | return con == 1; | ||
| 538 | } | ||
| 539 | |||
| 540 | /* s3c2410_pm_restore_gpio() | ||
| 541 | * | ||
| 542 | * Restore one of the GPIO banks that was saved during suspend. This is | ||
| 543 | * not as simple as once thought, due to the possibility of glitches | ||
| 544 | * from the order that the CON and DAT registers are set in. | ||
| 545 | * | ||
| 546 | * The three states the pin can be are {IN,OUT,SFN} which gives us 9 | ||
| 547 | * combinations of changes to check. Three of these, if the pin stays | ||
| 548 | * in the same configuration can be discounted. This leaves us with | ||
| 549 | * the following: | ||
| 550 | * | ||
| 551 | * { IN => OUT } Change DAT first | ||
| 552 | * { IN => SFN } Change CON first | ||
| 553 | * { OUT => SFN } Change CON first, so new data will not glitch | ||
| 554 | * { OUT => IN } Change CON first, so new data will not glitch | ||
| 555 | * { SFN => IN } Change CON first | ||
| 556 | * { SFN => OUT } Change DAT first, so new data will not glitch [1] | ||
| 557 | * | ||
| 558 | * We do not currently deal with the UP registers as these control | ||
| 559 | * weak resistors, so a small delay in change should not need to bring | ||
| 560 | * these into the calculations. | ||
| 561 | * | ||
| 562 | * [1] this assumes that writing to a pin DAT whilst in SFN will set the | ||
| 563 | * state for when it is next output. | ||
| 564 | */ | ||
| 565 | |||
| 566 | static void s3c2410_pm_restore_gpio(int index, struct gpio_sleep *gps) | ||
| 567 | { | ||
| 568 | void __iomem *base = gps->base; | ||
| 569 | unsigned long gps_gpcon = gps->gpcon; | ||
| 570 | unsigned long gps_gpdat = gps->gpdat; | ||
| 571 | unsigned long old_gpcon; | ||
| 572 | unsigned long old_gpdat; | ||
| 573 | unsigned long old_gpup = 0x0; | ||
| 574 | unsigned long gpcon; | ||
| 575 | int nr; | ||
| 576 | |||
| 577 | old_gpcon = __raw_readl(base + OFFS_CON); | ||
| 578 | old_gpdat = __raw_readl(base + OFFS_DAT); | ||
| 579 | |||
| 580 | if (base == S3C2410_GPACON) { | ||
| 581 | /* GPACON only has one bit per control / data and no PULLUPs. | ||
| 582 | * GPACON[x] = 0 => Output, 1 => SFN */ | ||
| 583 | |||
| 584 | /* first set all SFN bits to SFN */ | ||
| 585 | |||
| 586 | gpcon = old_gpcon | gps->gpcon; | ||
| 587 | __raw_writel(gpcon, base + OFFS_CON); | ||
| 588 | |||
| 589 | /* now set all the other bits */ | ||
| 590 | |||
| 591 | __raw_writel(gps_gpdat, base + OFFS_DAT); | ||
| 592 | __raw_writel(gps_gpcon, base + OFFS_CON); | ||
| 593 | } else { | ||
| 594 | unsigned long old, new, mask; | ||
| 595 | unsigned long change_mask = 0x0; | ||
| 596 | |||
| 597 | old_gpup = __raw_readl(base + OFFS_UP); | ||
| 598 | |||
| 599 | /* Create a change_mask of all the items that need to have | ||
| 600 | * their CON value changed before their DAT value, so that | ||
| 601 | * we minimise the work between the two settings. | ||
| 602 | */ | ||
| 603 | |||
| 604 | for (nr = 0, mask = 0x03; nr < 32; nr += 2, mask <<= 2) { | ||
| 605 | old = (old_gpcon & mask) >> nr; | ||
| 606 | new = (gps_gpcon & mask) >> nr; | ||
| 607 | |||
| 608 | /* If there is no change, then skip */ | ||
| 609 | |||
| 610 | if (old == new) | ||
| 611 | continue; | ||
| 612 | |||
| 613 | /* If both are special function, then skip */ | ||
| 614 | |||
| 615 | if (is_sfn(old) && is_sfn(new)) | ||
| 616 | continue; | ||
| 617 | |||
| 618 | /* Change is IN => OUT, do not change now */ | ||
| 619 | |||
| 620 | if (is_in(old) && is_out(new)) | ||
| 621 | continue; | ||
| 622 | |||
| 623 | /* Change is SFN => OUT, do not change now */ | ||
| 624 | |||
| 625 | if (is_sfn(old) && is_out(new)) | ||
| 626 | continue; | ||
| 627 | |||
| 628 | /* We should now be at the case of IN=>SFN, | ||
| 629 | * OUT=>SFN, OUT=>IN, SFN=>IN. */ | ||
| 630 | |||
| 631 | change_mask |= mask; | ||
| 632 | } | ||
| 633 | |||
| 634 | /* Write the new CON settings */ | ||
| 635 | |||
| 636 | gpcon = old_gpcon & ~change_mask; | ||
| 637 | gpcon |= gps_gpcon & change_mask; | ||
| 638 | |||
| 639 | __raw_writel(gpcon, base + OFFS_CON); | ||
| 640 | |||
| 641 | /* Now change any items that require DAT,CON */ | ||
| 642 | |||
| 643 | __raw_writel(gps_gpdat, base + OFFS_DAT); | ||
| 644 | __raw_writel(gps_gpcon, base + OFFS_CON); | ||
| 645 | __raw_writel(gps->gpup, base + OFFS_UP); | ||
| 646 | } | ||
| 647 | |||
| 648 | DBG("GPIO[%d] CON %08lx => %08lx, DAT %08lx => %08lx\n", | ||
| 649 | index, old_gpcon, gps_gpcon, old_gpdat, gps_gpdat); | ||
| 650 | } | ||
| 651 | |||
| 652 | |||
| 653 | /** s3c2410_pm_restore_gpios() | ||
| 654 | * | ||
| 655 | * Restore the state of the GPIOs | ||
| 656 | */ | ||
| 657 | |||
| 658 | static void s3c2410_pm_restore_gpios(void) | ||
| 659 | { | ||
| 660 | struct gpio_sleep *gps = gpio_save; | ||
| 661 | int gpio; | ||
| 662 | |||
| 663 | for (gpio = 0; gpio < ARRAY_SIZE(gpio_save); gpio++, gps++) { | ||
| 664 | s3c2410_pm_restore_gpio(gpio, gps); | ||
| 665 | } | ||
| 666 | } | ||
| 667 | |||
| 489 | void (*pm_cpu_prep)(void); | 668 | void (*pm_cpu_prep)(void); |
| 490 | void (*pm_cpu_sleep)(void); | 669 | void (*pm_cpu_sleep)(void); |
| 491 | 670 | ||
| @@ -535,7 +714,8 @@ static int s3c2410_pm_enter(suspend_state_t state) | |||
| 535 | 714 | ||
| 536 | /* save all necessary core registers not covered by the drivers */ | 715 | /* save all necessary core registers not covered by the drivers */ |
| 537 | 716 | ||
| 538 | s3c2410_pm_do_save(gpio_save, ARRAY_SIZE(gpio_save)); | 717 | s3c2410_pm_save_gpios(); |
| 718 | s3c2410_pm_do_save(misc_save, ARRAY_SIZE(misc_save)); | ||
| 539 | s3c2410_pm_do_save(core_save, ARRAY_SIZE(core_save)); | 719 | s3c2410_pm_do_save(core_save, ARRAY_SIZE(core_save)); |
| 540 | s3c2410_pm_do_save(uart_save, ARRAY_SIZE(uart_save)); | 720 | s3c2410_pm_do_save(uart_save, ARRAY_SIZE(uart_save)); |
| 541 | 721 | ||
| @@ -585,8 +765,9 @@ static int s3c2410_pm_enter(suspend_state_t state) | |||
| 585 | /* restore the system state */ | 765 | /* restore the system state */ |
| 586 | 766 | ||
| 587 | s3c2410_pm_do_restore_core(core_save, ARRAY_SIZE(core_save)); | 767 | s3c2410_pm_do_restore_core(core_save, ARRAY_SIZE(core_save)); |
| 588 | s3c2410_pm_do_restore(gpio_save, ARRAY_SIZE(gpio_save)); | 768 | s3c2410_pm_do_restore(misc_save, ARRAY_SIZE(misc_save)); |
| 589 | s3c2410_pm_do_restore(uart_save, ARRAY_SIZE(uart_save)); | 769 | s3c2410_pm_do_restore(uart_save, ARRAY_SIZE(uart_save)); |
| 770 | s3c2410_pm_restore_gpios(); | ||
| 590 | 771 | ||
| 591 | s3c2410_pm_debug_init(); | 772 | s3c2410_pm_debug_init(); |
| 592 | 773 | ||
