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 | ||