diff options
Diffstat (limited to 'drivers/acpi/sleep.c')
-rw-r--r-- | drivers/acpi/sleep.c | 152 |
1 files changed, 147 insertions, 5 deletions
diff --git a/drivers/acpi/sleep.c b/drivers/acpi/sleep.c index 097d630ab886..be17664736b2 100644 --- a/drivers/acpi/sleep.c +++ b/drivers/acpi/sleep.c | |||
@@ -650,38 +650,165 @@ static const struct platform_suspend_ops acpi_suspend_ops_old = { | |||
650 | .recover = acpi_pm_finish, | 650 | .recover = acpi_pm_finish, |
651 | }; | 651 | }; |
652 | 652 | ||
653 | static bool s2idle_in_progress; | ||
654 | static bool s2idle_wakeup; | ||
655 | |||
656 | /* | ||
657 | * On platforms supporting the Low Power S0 Idle interface there is an ACPI | ||
658 | * device object with the PNP0D80 compatible device ID (System Power Management | ||
659 | * Controller) and a specific _DSM method under it. That method, if present, | ||
660 | * can be used to indicate to the platform that the OS is transitioning into a | ||
661 | * low-power state in which certain types of activity are not desirable or that | ||
662 | * it is leaving such a state, which allows the platform to adjust its operation | ||
663 | * mode accordingly. | ||
664 | */ | ||
665 | static const struct acpi_device_id lps0_device_ids[] = { | ||
666 | {"PNP0D80", }, | ||
667 | {"", }, | ||
668 | }; | ||
669 | |||
670 | #define ACPI_LPS0_DSM_UUID "c4eb40a0-6cd2-11e2-bcfd-0800200c9a66" | ||
671 | |||
672 | #define ACPI_LPS0_SCREEN_OFF 3 | ||
673 | #define ACPI_LPS0_SCREEN_ON 4 | ||
674 | #define ACPI_LPS0_ENTRY 5 | ||
675 | #define ACPI_LPS0_EXIT 6 | ||
676 | |||
677 | #define ACPI_S2IDLE_FUNC_MASK ((1 << ACPI_LPS0_ENTRY) | (1 << ACPI_LPS0_EXIT)) | ||
678 | |||
679 | static acpi_handle lps0_device_handle; | ||
680 | static guid_t lps0_dsm_guid; | ||
681 | static char lps0_dsm_func_mask; | ||
682 | |||
683 | static void acpi_sleep_run_lps0_dsm(unsigned int func) | ||
684 | { | ||
685 | union acpi_object *out_obj; | ||
686 | |||
687 | if (!(lps0_dsm_func_mask & (1 << func))) | ||
688 | return; | ||
689 | |||
690 | out_obj = acpi_evaluate_dsm(lps0_device_handle, &lps0_dsm_guid, 1, func, NULL); | ||
691 | ACPI_FREE(out_obj); | ||
692 | |||
693 | acpi_handle_debug(lps0_device_handle, "_DSM function %u evaluation %s\n", | ||
694 | func, out_obj ? "successful" : "failed"); | ||
695 | } | ||
696 | |||
697 | static int lps0_device_attach(struct acpi_device *adev, | ||
698 | const struct acpi_device_id *not_used) | ||
699 | { | ||
700 | union acpi_object *out_obj; | ||
701 | |||
702 | if (lps0_device_handle) | ||
703 | return 0; | ||
704 | |||
705 | if (!(acpi_gbl_FADT.flags & ACPI_FADT_LOW_POWER_S0)) | ||
706 | return 0; | ||
707 | |||
708 | guid_parse(ACPI_LPS0_DSM_UUID, &lps0_dsm_guid); | ||
709 | /* Check if the _DSM is present and as expected. */ | ||
710 | out_obj = acpi_evaluate_dsm(adev->handle, &lps0_dsm_guid, 1, 0, NULL); | ||
711 | if (out_obj && out_obj->type == ACPI_TYPE_BUFFER) { | ||
712 | char bitmask = *(char *)out_obj->buffer.pointer; | ||
713 | |||
714 | if ((bitmask & ACPI_S2IDLE_FUNC_MASK) == ACPI_S2IDLE_FUNC_MASK) { | ||
715 | lps0_dsm_func_mask = bitmask; | ||
716 | lps0_device_handle = adev->handle; | ||
717 | } | ||
718 | |||
719 | acpi_handle_debug(adev->handle, "_DSM function mask: 0x%x\n", | ||
720 | bitmask); | ||
721 | } else { | ||
722 | acpi_handle_debug(adev->handle, | ||
723 | "_DSM function 0 evaluation failed\n"); | ||
724 | } | ||
725 | ACPI_FREE(out_obj); | ||
726 | return 0; | ||
727 | } | ||
728 | |||
729 | static struct acpi_scan_handler lps0_handler = { | ||
730 | .ids = lps0_device_ids, | ||
731 | .attach = lps0_device_attach, | ||
732 | }; | ||
733 | |||
653 | static int acpi_freeze_begin(void) | 734 | static int acpi_freeze_begin(void) |
654 | { | 735 | { |
655 | acpi_scan_lock_acquire(); | 736 | acpi_scan_lock_acquire(); |
737 | s2idle_in_progress = true; | ||
656 | return 0; | 738 | return 0; |
657 | } | 739 | } |
658 | 740 | ||
659 | static int acpi_freeze_prepare(void) | 741 | static int acpi_freeze_prepare(void) |
660 | { | 742 | { |
661 | acpi_enable_wakeup_devices(ACPI_STATE_S0); | 743 | if (lps0_device_handle) { |
662 | acpi_enable_all_wakeup_gpes(); | 744 | acpi_sleep_run_lps0_dsm(ACPI_LPS0_SCREEN_OFF); |
663 | acpi_os_wait_events_complete(); | 745 | acpi_sleep_run_lps0_dsm(ACPI_LPS0_ENTRY); |
746 | } else { | ||
747 | /* | ||
748 | * The configuration of GPEs is changed here to avoid spurious | ||
749 | * wakeups, but that should not be necessary if this is a | ||
750 | * "low-power S0" platform and the low-power S0 _DSM is present. | ||
751 | */ | ||
752 | acpi_enable_all_wakeup_gpes(); | ||
753 | acpi_os_wait_events_complete(); | ||
754 | } | ||
664 | if (acpi_sci_irq_valid()) | 755 | if (acpi_sci_irq_valid()) |
665 | enable_irq_wake(acpi_sci_irq); | 756 | enable_irq_wake(acpi_sci_irq); |
757 | |||
666 | return 0; | 758 | return 0; |
667 | } | 759 | } |
668 | 760 | ||
761 | static void acpi_freeze_wake(void) | ||
762 | { | ||
763 | /* | ||
764 | * If IRQD_WAKEUP_ARMED is not set for the SCI at this point, it means | ||
765 | * that the SCI has triggered while suspended, so cancel the wakeup in | ||
766 | * case it has not been a wakeup event (the GPEs will be checked later). | ||
767 | */ | ||
768 | if (acpi_sci_irq_valid() && | ||
769 | !irqd_is_wakeup_armed(irq_get_irq_data(acpi_sci_irq))) { | ||
770 | pm_system_cancel_wakeup(); | ||
771 | s2idle_wakeup = true; | ||
772 | } | ||
773 | } | ||
774 | |||
775 | static void acpi_freeze_sync(void) | ||
776 | { | ||
777 | /* | ||
778 | * Process all pending events in case there are any wakeup ones. | ||
779 | * | ||
780 | * The EC driver uses the system workqueue, so that one needs to be | ||
781 | * flushed too. | ||
782 | */ | ||
783 | acpi_os_wait_events_complete(); | ||
784 | flush_scheduled_work(); | ||
785 | s2idle_wakeup = false; | ||
786 | } | ||
787 | |||
669 | static void acpi_freeze_restore(void) | 788 | static void acpi_freeze_restore(void) |
670 | { | 789 | { |
671 | acpi_disable_wakeup_devices(ACPI_STATE_S0); | ||
672 | if (acpi_sci_irq_valid()) | 790 | if (acpi_sci_irq_valid()) |
673 | disable_irq_wake(acpi_sci_irq); | 791 | disable_irq_wake(acpi_sci_irq); |
674 | acpi_enable_all_runtime_gpes(); | 792 | |
793 | if (lps0_device_handle) { | ||
794 | acpi_sleep_run_lps0_dsm(ACPI_LPS0_EXIT); | ||
795 | acpi_sleep_run_lps0_dsm(ACPI_LPS0_SCREEN_ON); | ||
796 | } else { | ||
797 | acpi_enable_all_runtime_gpes(); | ||
798 | } | ||
675 | } | 799 | } |
676 | 800 | ||
677 | static void acpi_freeze_end(void) | 801 | static void acpi_freeze_end(void) |
678 | { | 802 | { |
803 | s2idle_in_progress = false; | ||
679 | acpi_scan_lock_release(); | 804 | acpi_scan_lock_release(); |
680 | } | 805 | } |
681 | 806 | ||
682 | static const struct platform_freeze_ops acpi_freeze_ops = { | 807 | static const struct platform_freeze_ops acpi_freeze_ops = { |
683 | .begin = acpi_freeze_begin, | 808 | .begin = acpi_freeze_begin, |
684 | .prepare = acpi_freeze_prepare, | 809 | .prepare = acpi_freeze_prepare, |
810 | .wake = acpi_freeze_wake, | ||
811 | .sync = acpi_freeze_sync, | ||
685 | .restore = acpi_freeze_restore, | 812 | .restore = acpi_freeze_restore, |
686 | .end = acpi_freeze_end, | 813 | .end = acpi_freeze_end, |
687 | }; | 814 | }; |
@@ -696,13 +823,28 @@ static void acpi_sleep_suspend_setup(void) | |||
696 | 823 | ||
697 | suspend_set_ops(old_suspend_ordering ? | 824 | suspend_set_ops(old_suspend_ordering ? |
698 | &acpi_suspend_ops_old : &acpi_suspend_ops); | 825 | &acpi_suspend_ops_old : &acpi_suspend_ops); |
826 | |||
827 | acpi_scan_add_handler(&lps0_handler); | ||
699 | freeze_set_ops(&acpi_freeze_ops); | 828 | freeze_set_ops(&acpi_freeze_ops); |
700 | } | 829 | } |
701 | 830 | ||
702 | #else /* !CONFIG_SUSPEND */ | 831 | #else /* !CONFIG_SUSPEND */ |
832 | #define s2idle_in_progress (false) | ||
833 | #define s2idle_wakeup (false) | ||
834 | #define lps0_device_handle (NULL) | ||
703 | static inline void acpi_sleep_suspend_setup(void) {} | 835 | static inline void acpi_sleep_suspend_setup(void) {} |
704 | #endif /* !CONFIG_SUSPEND */ | 836 | #endif /* !CONFIG_SUSPEND */ |
705 | 837 | ||
838 | bool acpi_s2idle_wakeup(void) | ||
839 | { | ||
840 | return s2idle_wakeup; | ||
841 | } | ||
842 | |||
843 | bool acpi_sleep_no_ec_events(void) | ||
844 | { | ||
845 | return !s2idle_in_progress || !lps0_device_handle; | ||
846 | } | ||
847 | |||
706 | #ifdef CONFIG_PM_SLEEP | 848 | #ifdef CONFIG_PM_SLEEP |
707 | static u32 saved_bm_rld; | 849 | static u32 saved_bm_rld; |
708 | 850 | ||