Merge branch 'for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/ebiederm...
[platform/kernel/linux-starfive.git] / drivers / acpi / sleep.c
index 097d630..be17664 100644 (file)
@@ -650,38 +650,165 @@ static const struct platform_suspend_ops acpi_suspend_ops_old = {
        .recover = acpi_pm_finish,
 };
 
+static bool s2idle_in_progress;
+static bool s2idle_wakeup;
+
+/*
+ * On platforms supporting the Low Power S0 Idle interface there is an ACPI
+ * device object with the PNP0D80 compatible device ID (System Power Management
+ * Controller) and a specific _DSM method under it.  That method, if present,
+ * can be used to indicate to the platform that the OS is transitioning into a
+ * low-power state in which certain types of activity are not desirable or that
+ * it is leaving such a state, which allows the platform to adjust its operation
+ * mode accordingly.
+ */
+static const struct acpi_device_id lps0_device_ids[] = {
+       {"PNP0D80", },
+       {"", },
+};
+
+#define ACPI_LPS0_DSM_UUID     "c4eb40a0-6cd2-11e2-bcfd-0800200c9a66"
+
+#define ACPI_LPS0_SCREEN_OFF   3
+#define ACPI_LPS0_SCREEN_ON    4
+#define ACPI_LPS0_ENTRY                5
+#define ACPI_LPS0_EXIT         6
+
+#define ACPI_S2IDLE_FUNC_MASK  ((1 << ACPI_LPS0_ENTRY) | (1 << ACPI_LPS0_EXIT))
+
+static acpi_handle lps0_device_handle;
+static guid_t lps0_dsm_guid;
+static char lps0_dsm_func_mask;
+
+static void acpi_sleep_run_lps0_dsm(unsigned int func)
+{
+       union acpi_object *out_obj;
+
+       if (!(lps0_dsm_func_mask & (1 << func)))
+               return;
+
+       out_obj = acpi_evaluate_dsm(lps0_device_handle, &lps0_dsm_guid, 1, func, NULL);
+       ACPI_FREE(out_obj);
+
+       acpi_handle_debug(lps0_device_handle, "_DSM function %u evaluation %s\n",
+                         func, out_obj ? "successful" : "failed");
+}
+
+static int lps0_device_attach(struct acpi_device *adev,
+                             const struct acpi_device_id *not_used)
+{
+       union acpi_object *out_obj;
+
+       if (lps0_device_handle)
+               return 0;
+
+       if (!(acpi_gbl_FADT.flags & ACPI_FADT_LOW_POWER_S0))
+               return 0;
+
+       guid_parse(ACPI_LPS0_DSM_UUID, &lps0_dsm_guid);
+       /* Check if the _DSM is present and as expected. */
+       out_obj = acpi_evaluate_dsm(adev->handle, &lps0_dsm_guid, 1, 0, NULL);
+       if (out_obj && out_obj->type == ACPI_TYPE_BUFFER) {
+               char bitmask = *(char *)out_obj->buffer.pointer;
+
+               if ((bitmask & ACPI_S2IDLE_FUNC_MASK) == ACPI_S2IDLE_FUNC_MASK) {
+                       lps0_dsm_func_mask = bitmask;
+                       lps0_device_handle = adev->handle;
+               }
+
+               acpi_handle_debug(adev->handle, "_DSM function mask: 0x%x\n",
+                                 bitmask);
+       } else {
+               acpi_handle_debug(adev->handle,
+                                 "_DSM function 0 evaluation failed\n");
+       }
+       ACPI_FREE(out_obj);
+       return 0;
+}
+
+static struct acpi_scan_handler lps0_handler = {
+       .ids = lps0_device_ids,
+       .attach = lps0_device_attach,
+};
+
 static int acpi_freeze_begin(void)
 {
        acpi_scan_lock_acquire();
+       s2idle_in_progress = true;
        return 0;
 }
 
 static int acpi_freeze_prepare(void)
 {
-       acpi_enable_wakeup_devices(ACPI_STATE_S0);
-       acpi_enable_all_wakeup_gpes();
-       acpi_os_wait_events_complete();
+       if (lps0_device_handle) {
+               acpi_sleep_run_lps0_dsm(ACPI_LPS0_SCREEN_OFF);
+               acpi_sleep_run_lps0_dsm(ACPI_LPS0_ENTRY);
+       } else {
+               /*
+                * The configuration of GPEs is changed here to avoid spurious
+                * wakeups, but that should not be necessary if this is a
+                * "low-power S0" platform and the low-power S0 _DSM is present.
+                */
+               acpi_enable_all_wakeup_gpes();
+               acpi_os_wait_events_complete();
+       }
        if (acpi_sci_irq_valid())
                enable_irq_wake(acpi_sci_irq);
+
        return 0;
 }
 
+static void acpi_freeze_wake(void)
+{
+       /*
+        * If IRQD_WAKEUP_ARMED is not set for the SCI at this point, it means
+        * that the SCI has triggered while suspended, so cancel the wakeup in
+        * case it has not been a wakeup event (the GPEs will be checked later).
+        */
+       if (acpi_sci_irq_valid() &&
+           !irqd_is_wakeup_armed(irq_get_irq_data(acpi_sci_irq))) {
+               pm_system_cancel_wakeup();
+               s2idle_wakeup = true;
+       }
+}
+
+static void acpi_freeze_sync(void)
+{
+       /*
+        * Process all pending events in case there are any wakeup ones.
+        *
+        * The EC driver uses the system workqueue, so that one needs to be
+        * flushed too.
+        */
+       acpi_os_wait_events_complete();
+       flush_scheduled_work();
+       s2idle_wakeup = false;
+}
+
 static void acpi_freeze_restore(void)
 {
-       acpi_disable_wakeup_devices(ACPI_STATE_S0);
        if (acpi_sci_irq_valid())
                disable_irq_wake(acpi_sci_irq);
-       acpi_enable_all_runtime_gpes();
+
+       if (lps0_device_handle) {
+               acpi_sleep_run_lps0_dsm(ACPI_LPS0_EXIT);
+               acpi_sleep_run_lps0_dsm(ACPI_LPS0_SCREEN_ON);
+       } else {
+               acpi_enable_all_runtime_gpes();
+       }
 }
 
 static void acpi_freeze_end(void)
 {
+       s2idle_in_progress = false;
        acpi_scan_lock_release();
 }
 
 static const struct platform_freeze_ops acpi_freeze_ops = {
        .begin = acpi_freeze_begin,
        .prepare = acpi_freeze_prepare,
+       .wake = acpi_freeze_wake,
+       .sync = acpi_freeze_sync,
        .restore = acpi_freeze_restore,
        .end = acpi_freeze_end,
 };
@@ -696,13 +823,28 @@ static void acpi_sleep_suspend_setup(void)
 
        suspend_set_ops(old_suspend_ordering ?
                &acpi_suspend_ops_old : &acpi_suspend_ops);
+
+       acpi_scan_add_handler(&lps0_handler);
        freeze_set_ops(&acpi_freeze_ops);
 }
 
 #else /* !CONFIG_SUSPEND */
+#define s2idle_in_progress     (false)
+#define s2idle_wakeup          (false)
+#define lps0_device_handle     (NULL)
 static inline void acpi_sleep_suspend_setup(void) {}
 #endif /* !CONFIG_SUSPEND */
 
+bool acpi_s2idle_wakeup(void)
+{
+       return s2idle_wakeup;
+}
+
+bool acpi_sleep_no_ec_events(void)
+{
+       return !s2idle_in_progress || !lps0_device_handle;
+}
+
 #ifdef CONFIG_PM_SLEEP
 static u32 saved_bm_rld;