aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRafael J. Wysocki <rafael.j.wysocki@intel.com>2017-06-23 09:24:32 -0400
committerRafael J. Wysocki <rafael.j.wysocki@intel.com>2017-06-23 09:24:32 -0400
commit8110dd281e155e5010ffd657bba4742ebef7a93f (patch)
treed001fcff80a7e9597daf04d3a70ed6323f1701c5
parentef884112e55c60d9e208b6524ae1841ae7e2fb2c (diff)
ACPI / sleep: EC-based wakeup from suspend-to-idle on recent systems
Some recent Dell laptops, including the XPS13 model numbers 9360 and 9365, cannot be woken up from suspend-to-idle by pressing the power button which is unexpected and makes that feature less usable on those systems. Moreover, on the 9365 ACPI S3 (suspend-to-RAM) is not expected to be used at all (the OS these systems ship with never exercises the ACPI S3 path in the firmware) and suspend-to-idle is the only viable system suspend mechanism there. The reason why the power button wakeup from suspend-to-idle doesn't work on those systems is because their power button events are signaled by the EC (Embedded Controller), whose GPE (General Purpose Event) line is disabled during suspend-to-idle transitions in Linux. That is done on purpose, because in general the EC tends to be noisy for various reasons (battery and thermal updates and similar, for example) and all events signaled by it would kick the CPUs out of deep idle states while in suspend-to-idle, which effectively might defeat its purpose. Of course, on the Dell systems in question the EC GPE must be enabled during suspend-to-idle transitions for the button press events to be signaled while suspended at all, but fortunately there is a way out of this puzzle. First of all, those systems have the ACPI_FADT_LOW_POWER_S0 flag set in their ACPI tables, which means that the OS is expected to prefer the "low power S0 idle" system state over ACPI S3 on them. That causes the most recent versions of other OSes to simply ignore ACPI S3 on those systems, so it is reasonable to expect that it should not be necessary to block GPEs during suspend-to-idle on them. Second, in addition to that, the systems in question provide a special firmware interface that can be used to indicate to the platform that the OS is transitioning into a system-wide low-power state in which certain types of activity are not desirable or that it is leaving such a state and that (in principle) should allow the platform to adjust its operation mode accordingly. That interface is a special _DSM object under a System Power Management Controller device (PNP0D80). The expected way to use it is to invoke function 0 from it on system initialization, functions 3 and 5 during suspend transitions and functions 4 and 6 during resume transitions (to reverse the actions carried out by the former). In particular, function 5 from the "Low-Power S0" device _DSM is expected to cause the platform to put itself into a low-power operation mode which should include making the EC less verbose (so to speak). Next, on resume, function 6 switches the platform back to the "working-state" operation mode. In accordance with the above, modify the ACPI suspend-to-idle code to look for the "Low-Power S0" _DSM interface on platforms with the ACPI_FADT_LOW_POWER_S0 flag set in the ACPI tables. If it's there, use it during suspend-to-idle transitions as prescribed and avoid changing the GPE configuration in that case. [That should reflect what the most recent versions of other OSes do.] Also modify the ACPI EC driver to make it handle events during suspend-to-idle in the usual way if the "Low-Power S0" _DSM interface is going to be used to make the power button events work while suspended on the Dell machines mentioned above Link: http://www.uefi.org/sites/default/files/resources/Intel_ACPI_Low_Power_S0_Idle.pdf Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
-rw-r--r--drivers/acpi/ec.c2
-rw-r--r--drivers/acpi/internal.h2
-rw-r--r--drivers/acpi/sleep.c113
3 files changed, 112 insertions, 5 deletions
diff --git a/drivers/acpi/ec.c b/drivers/acpi/ec.c
index c24235d8fb52..156e15c35ffa 100644
--- a/drivers/acpi/ec.c
+++ b/drivers/acpi/ec.c
@@ -1835,7 +1835,7 @@ static int acpi_ec_suspend(struct device *dev)
1835 struct acpi_ec *ec = 1835 struct acpi_ec *ec =
1836 acpi_driver_data(to_acpi_device(dev)); 1836 acpi_driver_data(to_acpi_device(dev));
1837 1837
1838 if (ec_freeze_events) 1838 if (acpi_sleep_no_ec_events() && ec_freeze_events)
1839 acpi_ec_disable_event(ec); 1839 acpi_ec_disable_event(ec);
1840 return 0; 1840 return 0;
1841} 1841}
diff --git a/drivers/acpi/internal.h b/drivers/acpi/internal.h
index 75924ea69071..be79f7db1850 100644
--- a/drivers/acpi/internal.h
+++ b/drivers/acpi/internal.h
@@ -199,9 +199,11 @@ void acpi_ec_remove_query_handler(struct acpi_ec *ec, u8 query_bit);
199 -------------------------------------------------------------------------- */ 199 -------------------------------------------------------------------------- */
200#ifdef CONFIG_ACPI_SYSTEM_POWER_STATES_SUPPORT 200#ifdef CONFIG_ACPI_SYSTEM_POWER_STATES_SUPPORT
201extern bool acpi_s2idle_wakeup(void); 201extern bool acpi_s2idle_wakeup(void);
202extern bool acpi_sleep_no_ec_events(void);
202extern int acpi_sleep_init(void); 203extern int acpi_sleep_init(void);
203#else 204#else
204static inline bool acpi_s2idle_wakeup(void) { return false; } 205static inline bool acpi_s2idle_wakeup(void) { return false; }
206static inline bool acpi_sleep_no_ec_events(void) { return true; }
205static inline int acpi_sleep_init(void) { return -ENXIO; } 207static inline int acpi_sleep_init(void) { return -ENXIO; }
206#endif 208#endif
207 209
diff --git a/drivers/acpi/sleep.c b/drivers/acpi/sleep.c
index 555de11a56b6..be17664736b2 100644
--- a/drivers/acpi/sleep.c
+++ b/drivers/acpi/sleep.c
@@ -650,18 +650,108 @@ static const struct platform_suspend_ops acpi_suspend_ops_old = {
650 .recover = acpi_pm_finish, 650 .recover = acpi_pm_finish,
651}; 651};
652 652
653static bool s2idle_in_progress;
653static bool s2idle_wakeup; 654static bool s2idle_wakeup;
654 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 */
665static 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
679static acpi_handle lps0_device_handle;
680static guid_t lps0_dsm_guid;
681static char lps0_dsm_func_mask;
682
683static 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
697static 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
729static struct acpi_scan_handler lps0_handler = {
730 .ids = lps0_device_ids,
731 .attach = lps0_device_attach,
732};
733
655static int acpi_freeze_begin(void) 734static int acpi_freeze_begin(void)
656{ 735{
657 acpi_scan_lock_acquire(); 736 acpi_scan_lock_acquire();
737 s2idle_in_progress = true;
658 return 0; 738 return 0;
659} 739}
660 740
661static int acpi_freeze_prepare(void) 741static int acpi_freeze_prepare(void)
662{ 742{
663 acpi_enable_all_wakeup_gpes(); 743 if (lps0_device_handle) {
664 acpi_os_wait_events_complete(); 744 acpi_sleep_run_lps0_dsm(ACPI_LPS0_SCREEN_OFF);
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 }
665 if (acpi_sci_irq_valid()) 755 if (acpi_sci_irq_valid())
666 enable_irq_wake(acpi_sci_irq); 756 enable_irq_wake(acpi_sci_irq);
667 757
@@ -700,11 +790,17 @@ static void acpi_freeze_restore(void)
700 if (acpi_sci_irq_valid()) 790 if (acpi_sci_irq_valid())
701 disable_irq_wake(acpi_sci_irq); 791 disable_irq_wake(acpi_sci_irq);
702 792
703 acpi_enable_all_runtime_gpes(); 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 }
704} 799}
705 800
706static void acpi_freeze_end(void) 801static void acpi_freeze_end(void)
707{ 802{
803 s2idle_in_progress = false;
708 acpi_scan_lock_release(); 804 acpi_scan_lock_release();
709} 805}
710 806
@@ -727,11 +823,15 @@ static void acpi_sleep_suspend_setup(void)
727 823
728 suspend_set_ops(old_suspend_ordering ? 824 suspend_set_ops(old_suspend_ordering ?
729 &acpi_suspend_ops_old : &acpi_suspend_ops); 825 &acpi_suspend_ops_old : &acpi_suspend_ops);
826
827 acpi_scan_add_handler(&lps0_handler);
730 freeze_set_ops(&acpi_freeze_ops); 828 freeze_set_ops(&acpi_freeze_ops);
731} 829}
732 830
733#else /* !CONFIG_SUSPEND */ 831#else /* !CONFIG_SUSPEND */
734#define s2idle_wakeup (false) 832#define s2idle_in_progress (false)
833#define s2idle_wakeup (false)
834#define lps0_device_handle (NULL)
735static inline void acpi_sleep_suspend_setup(void) {} 835static inline void acpi_sleep_suspend_setup(void) {}
736#endif /* !CONFIG_SUSPEND */ 836#endif /* !CONFIG_SUSPEND */
737 837
@@ -740,6 +840,11 @@ bool acpi_s2idle_wakeup(void)
740 return s2idle_wakeup; 840 return s2idle_wakeup;
741} 841}
742 842
843bool acpi_sleep_no_ec_events(void)
844{
845 return !s2idle_in_progress || !lps0_device_handle;
846}
847
743#ifdef CONFIG_PM_SLEEP 848#ifdef CONFIG_PM_SLEEP
744static u32 saved_bm_rld; 849static u32 saved_bm_rld;
745 850