Lenovo Slim 7 Pro 14ARH7 has a sporadically non-functional keyboard when resuming from s2idle. This is caused by some missing calls to the EC that don't occur in the AMD codepath but only in the Microsoft codepath. Add the system to the quirk list to force Microsoft codepath. Reported-by: Travis Glenn Hansen <travisghansen@yahoo.com> Reported-by: Sebastian S. <iam@decentr.al> Link: https://bugzilla.kernel.org/show_bug.cgi?id=216473 Link: https://bugzilla.kernel.org/show_bug.cgi?id=216438 Signed-off-by: Mario Limonciello <mario.limonciello@amd.com> Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
644 lines
17 KiB
C
644 lines
17 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* Architecture-specific ACPI-based support for suspend-to-idle.
|
|
*
|
|
* Author: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
|
|
* Author: Srinivas Pandruvada <srinivas.pandruvada@linux.intel.com>
|
|
* Author: Shyam Sundar S K <Shyam-sundar.S-k@amd.com>
|
|
*
|
|
* 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.
|
|
*/
|
|
|
|
#include <linux/acpi.h>
|
|
#include <linux/device.h>
|
|
#include <linux/dmi.h>
|
|
#include <linux/suspend.h>
|
|
|
|
#include "../sleep.h"
|
|
|
|
#ifdef CONFIG_SUSPEND
|
|
|
|
static bool sleep_no_lps0 __read_mostly;
|
|
module_param(sleep_no_lps0, bool, 0644);
|
|
MODULE_PARM_DESC(sleep_no_lps0, "Do not use the special LPS0 device interface");
|
|
|
|
static bool prefer_microsoft_dsm_guid __read_mostly;
|
|
module_param(prefer_microsoft_dsm_guid, bool, 0644);
|
|
MODULE_PARM_DESC(prefer_microsoft_dsm_guid, "Prefer using Microsoft GUID in LPS0 device _DSM evaluation");
|
|
|
|
static const struct acpi_device_id lps0_device_ids[] = {
|
|
{"PNP0D80", },
|
|
{"", },
|
|
};
|
|
|
|
/* Microsoft platform agnostic UUID */
|
|
#define ACPI_LPS0_DSM_UUID_MICROSOFT "11e00d56-ce64-47ce-837b-1f898f9aa461"
|
|
|
|
#define ACPI_LPS0_DSM_UUID "c4eb40a0-6cd2-11e2-bcfd-0800200c9a66"
|
|
|
|
#define ACPI_LPS0_GET_DEVICE_CONSTRAINTS 1
|
|
#define ACPI_LPS0_SCREEN_OFF 3
|
|
#define ACPI_LPS0_SCREEN_ON 4
|
|
#define ACPI_LPS0_ENTRY 5
|
|
#define ACPI_LPS0_EXIT 6
|
|
#define ACPI_LPS0_MS_ENTRY 7
|
|
#define ACPI_LPS0_MS_EXIT 8
|
|
|
|
/* AMD */
|
|
#define ACPI_LPS0_DSM_UUID_AMD "e3f32452-febc-43ce-9039-932122d37721"
|
|
#define ACPI_LPS0_ENTRY_AMD 2
|
|
#define ACPI_LPS0_EXIT_AMD 3
|
|
#define ACPI_LPS0_SCREEN_OFF_AMD 4
|
|
#define ACPI_LPS0_SCREEN_ON_AMD 5
|
|
|
|
static acpi_handle lps0_device_handle;
|
|
static guid_t lps0_dsm_guid;
|
|
static int lps0_dsm_func_mask;
|
|
|
|
static guid_t lps0_dsm_guid_microsoft;
|
|
static int lps0_dsm_func_mask_microsoft;
|
|
|
|
/* Device constraint entry structure */
|
|
struct lpi_device_info {
|
|
char *name;
|
|
int enabled;
|
|
union acpi_object *package;
|
|
};
|
|
|
|
/* Constraint package structure */
|
|
struct lpi_device_constraint {
|
|
int uid;
|
|
int min_dstate;
|
|
int function_states;
|
|
};
|
|
|
|
struct lpi_constraints {
|
|
acpi_handle handle;
|
|
int min_dstate;
|
|
};
|
|
|
|
/* AMD Constraint package structure */
|
|
struct lpi_device_constraint_amd {
|
|
char *name;
|
|
int enabled;
|
|
int function_states;
|
|
int min_dstate;
|
|
};
|
|
|
|
static LIST_HEAD(lps0_s2idle_devops_head);
|
|
|
|
static struct lpi_constraints *lpi_constraints_table;
|
|
static int lpi_constraints_table_size;
|
|
static int rev_id;
|
|
|
|
static void lpi_device_get_constraints_amd(void)
|
|
{
|
|
union acpi_object *out_obj;
|
|
int i, j, k;
|
|
|
|
out_obj = acpi_evaluate_dsm_typed(lps0_device_handle, &lps0_dsm_guid,
|
|
rev_id, ACPI_LPS0_GET_DEVICE_CONSTRAINTS,
|
|
NULL, ACPI_TYPE_PACKAGE);
|
|
|
|
acpi_handle_debug(lps0_device_handle, "_DSM function 1 eval %s\n",
|
|
out_obj ? "successful" : "failed");
|
|
|
|
if (!out_obj)
|
|
return;
|
|
|
|
for (i = 0; i < out_obj->package.count; i++) {
|
|
union acpi_object *package = &out_obj->package.elements[i];
|
|
|
|
if (package->type == ACPI_TYPE_PACKAGE) {
|
|
lpi_constraints_table = kcalloc(package->package.count,
|
|
sizeof(*lpi_constraints_table),
|
|
GFP_KERNEL);
|
|
|
|
if (!lpi_constraints_table)
|
|
goto free_acpi_buffer;
|
|
|
|
acpi_handle_debug(lps0_device_handle,
|
|
"LPI: constraints list begin:\n");
|
|
|
|
for (j = 0; j < package->package.count; ++j) {
|
|
union acpi_object *info_obj = &package->package.elements[j];
|
|
struct lpi_device_constraint_amd dev_info = {};
|
|
struct lpi_constraints *list;
|
|
acpi_status status;
|
|
|
|
for (k = 0; k < info_obj->package.count; ++k) {
|
|
union acpi_object *obj = &info_obj->package.elements[k];
|
|
|
|
list = &lpi_constraints_table[lpi_constraints_table_size];
|
|
list->min_dstate = -1;
|
|
|
|
switch (k) {
|
|
case 0:
|
|
dev_info.enabled = obj->integer.value;
|
|
break;
|
|
case 1:
|
|
dev_info.name = obj->string.pointer;
|
|
break;
|
|
case 2:
|
|
dev_info.function_states = obj->integer.value;
|
|
break;
|
|
case 3:
|
|
dev_info.min_dstate = obj->integer.value;
|
|
break;
|
|
}
|
|
|
|
if (!dev_info.enabled || !dev_info.name ||
|
|
!dev_info.min_dstate)
|
|
continue;
|
|
|
|
status = acpi_get_handle(NULL, dev_info.name,
|
|
&list->handle);
|
|
if (ACPI_FAILURE(status))
|
|
continue;
|
|
|
|
acpi_handle_debug(lps0_device_handle,
|
|
"Name:%s\n", dev_info.name);
|
|
|
|
list->min_dstate = dev_info.min_dstate;
|
|
|
|
if (list->min_dstate < 0) {
|
|
acpi_handle_debug(lps0_device_handle,
|
|
"Incomplete constraint defined\n");
|
|
continue;
|
|
}
|
|
}
|
|
lpi_constraints_table_size++;
|
|
}
|
|
}
|
|
}
|
|
|
|
acpi_handle_debug(lps0_device_handle, "LPI: constraints list end\n");
|
|
|
|
free_acpi_buffer:
|
|
ACPI_FREE(out_obj);
|
|
}
|
|
|
|
static void lpi_device_get_constraints(void)
|
|
{
|
|
union acpi_object *out_obj;
|
|
int i;
|
|
|
|
out_obj = acpi_evaluate_dsm_typed(lps0_device_handle, &lps0_dsm_guid,
|
|
1, ACPI_LPS0_GET_DEVICE_CONSTRAINTS,
|
|
NULL, ACPI_TYPE_PACKAGE);
|
|
|
|
acpi_handle_debug(lps0_device_handle, "_DSM function 1 eval %s\n",
|
|
out_obj ? "successful" : "failed");
|
|
|
|
if (!out_obj)
|
|
return;
|
|
|
|
lpi_constraints_table = kcalloc(out_obj->package.count,
|
|
sizeof(*lpi_constraints_table),
|
|
GFP_KERNEL);
|
|
if (!lpi_constraints_table)
|
|
goto free_acpi_buffer;
|
|
|
|
acpi_handle_debug(lps0_device_handle, "LPI: constraints list begin:\n");
|
|
|
|
for (i = 0; i < out_obj->package.count; i++) {
|
|
struct lpi_constraints *constraint;
|
|
acpi_status status;
|
|
union acpi_object *package = &out_obj->package.elements[i];
|
|
struct lpi_device_info info = { };
|
|
int package_count = 0, j;
|
|
|
|
if (!package)
|
|
continue;
|
|
|
|
for (j = 0; j < package->package.count; ++j) {
|
|
union acpi_object *element =
|
|
&(package->package.elements[j]);
|
|
|
|
switch (element->type) {
|
|
case ACPI_TYPE_INTEGER:
|
|
info.enabled = element->integer.value;
|
|
break;
|
|
case ACPI_TYPE_STRING:
|
|
info.name = element->string.pointer;
|
|
break;
|
|
case ACPI_TYPE_PACKAGE:
|
|
package_count = element->package.count;
|
|
info.package = element->package.elements;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!info.enabled || !info.package || !info.name)
|
|
continue;
|
|
|
|
constraint = &lpi_constraints_table[lpi_constraints_table_size];
|
|
|
|
status = acpi_get_handle(NULL, info.name, &constraint->handle);
|
|
if (ACPI_FAILURE(status))
|
|
continue;
|
|
|
|
acpi_handle_debug(lps0_device_handle,
|
|
"index:%d Name:%s\n", i, info.name);
|
|
|
|
constraint->min_dstate = -1;
|
|
|
|
for (j = 0; j < package_count; ++j) {
|
|
union acpi_object *info_obj = &info.package[j];
|
|
union acpi_object *cnstr_pkg;
|
|
union acpi_object *obj;
|
|
struct lpi_device_constraint dev_info;
|
|
|
|
switch (info_obj->type) {
|
|
case ACPI_TYPE_INTEGER:
|
|
/* version */
|
|
break;
|
|
case ACPI_TYPE_PACKAGE:
|
|
if (info_obj->package.count < 2)
|
|
break;
|
|
|
|
cnstr_pkg = info_obj->package.elements;
|
|
obj = &cnstr_pkg[0];
|
|
dev_info.uid = obj->integer.value;
|
|
obj = &cnstr_pkg[1];
|
|
dev_info.min_dstate = obj->integer.value;
|
|
|
|
acpi_handle_debug(lps0_device_handle,
|
|
"uid:%d min_dstate:%s\n",
|
|
dev_info.uid,
|
|
acpi_power_state_string(dev_info.min_dstate));
|
|
|
|
constraint->min_dstate = dev_info.min_dstate;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (constraint->min_dstate < 0) {
|
|
acpi_handle_debug(lps0_device_handle,
|
|
"Incomplete constraint defined\n");
|
|
continue;
|
|
}
|
|
|
|
lpi_constraints_table_size++;
|
|
}
|
|
|
|
acpi_handle_debug(lps0_device_handle, "LPI: constraints list end\n");
|
|
|
|
free_acpi_buffer:
|
|
ACPI_FREE(out_obj);
|
|
}
|
|
|
|
static void lpi_check_constraints(void)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < lpi_constraints_table_size; ++i) {
|
|
acpi_handle handle = lpi_constraints_table[i].handle;
|
|
struct acpi_device *adev = acpi_fetch_acpi_dev(handle);
|
|
|
|
if (!adev)
|
|
continue;
|
|
|
|
acpi_handle_debug(handle,
|
|
"LPI: required min power state:%s current power state:%s\n",
|
|
acpi_power_state_string(lpi_constraints_table[i].min_dstate),
|
|
acpi_power_state_string(adev->power.state));
|
|
|
|
if (!adev->flags.power_manageable) {
|
|
acpi_handle_info(handle, "LPI: Device not power manageable\n");
|
|
lpi_constraints_table[i].handle = NULL;
|
|
continue;
|
|
}
|
|
|
|
if (adev->power.state < lpi_constraints_table[i].min_dstate)
|
|
acpi_handle_info(handle,
|
|
"LPI: Constraint not met; min power state:%s current power state:%s\n",
|
|
acpi_power_state_string(lpi_constraints_table[i].min_dstate),
|
|
acpi_power_state_string(adev->power.state));
|
|
}
|
|
}
|
|
|
|
static void acpi_sleep_run_lps0_dsm(unsigned int func, unsigned int func_mask, guid_t dsm_guid)
|
|
{
|
|
union acpi_object *out_obj;
|
|
|
|
if (!(func_mask & (1 << func)))
|
|
return;
|
|
|
|
out_obj = acpi_evaluate_dsm(lps0_device_handle, &dsm_guid,
|
|
rev_id, func, NULL);
|
|
ACPI_FREE(out_obj);
|
|
|
|
acpi_handle_debug(lps0_device_handle, "_DSM function %u evaluation %s\n",
|
|
func, out_obj ? "successful" : "failed");
|
|
}
|
|
|
|
static bool acpi_s2idle_vendor_amd(void)
|
|
{
|
|
return boot_cpu_data.x86_vendor == X86_VENDOR_AMD;
|
|
}
|
|
|
|
static int validate_dsm(acpi_handle handle, const char *uuid, int rev, guid_t *dsm_guid)
|
|
{
|
|
union acpi_object *obj;
|
|
int ret = -EINVAL;
|
|
|
|
guid_parse(uuid, dsm_guid);
|
|
obj = acpi_evaluate_dsm(handle, dsm_guid, rev, 0, NULL);
|
|
|
|
/* Check if the _DSM is present and as expected. */
|
|
if (!obj || obj->type != ACPI_TYPE_BUFFER || obj->buffer.length == 0 ||
|
|
obj->buffer.length > sizeof(u32)) {
|
|
acpi_handle_debug(handle,
|
|
"_DSM UUID %s rev %d function 0 evaluation failed\n", uuid, rev);
|
|
goto out;
|
|
}
|
|
|
|
ret = *(int *)obj->buffer.pointer;
|
|
acpi_handle_debug(handle, "_DSM UUID %s rev %d function mask: 0x%x\n", uuid, rev, ret);
|
|
|
|
out:
|
|
ACPI_FREE(obj);
|
|
return ret;
|
|
}
|
|
|
|
struct amd_lps0_hid_device_data {
|
|
const unsigned int rev_id;
|
|
const bool check_off_by_one;
|
|
const bool prefer_amd_guid;
|
|
};
|
|
|
|
static const struct amd_lps0_hid_device_data amd_picasso = {
|
|
.rev_id = 0,
|
|
.check_off_by_one = true,
|
|
.prefer_amd_guid = false,
|
|
};
|
|
|
|
static const struct amd_lps0_hid_device_data amd_cezanne = {
|
|
.rev_id = 0,
|
|
.check_off_by_one = false,
|
|
.prefer_amd_guid = false,
|
|
};
|
|
|
|
static const struct amd_lps0_hid_device_data amd_rembrandt = {
|
|
.rev_id = 2,
|
|
.check_off_by_one = false,
|
|
.prefer_amd_guid = true,
|
|
};
|
|
|
|
static const struct acpi_device_id amd_hid_ids[] = {
|
|
{"AMD0004", (kernel_ulong_t)&amd_picasso, },
|
|
{"AMD0005", (kernel_ulong_t)&amd_picasso, },
|
|
{"AMDI0005", (kernel_ulong_t)&amd_picasso, },
|
|
{"AMDI0006", (kernel_ulong_t)&amd_cezanne, },
|
|
{"AMDI0007", (kernel_ulong_t)&amd_rembrandt, },
|
|
{}
|
|
};
|
|
|
|
static int lps0_prefer_microsoft(const struct dmi_system_id *id)
|
|
{
|
|
pr_debug("Preferring Microsoft GUID.\n");
|
|
prefer_microsoft_dsm_guid = true;
|
|
return 0;
|
|
}
|
|
|
|
static const struct dmi_system_id s2idle_dmi_table[] __initconst = {
|
|
{
|
|
/*
|
|
* ASUS TUF Gaming A17 FA707RE
|
|
* https://bugzilla.kernel.org/show_bug.cgi?id=216101
|
|
*/
|
|
.callback = lps0_prefer_microsoft,
|
|
.matches = {
|
|
DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."),
|
|
DMI_MATCH(DMI_PRODUCT_NAME, "ASUS TUF Gaming A17"),
|
|
},
|
|
},
|
|
{
|
|
/* ASUS ROG Zephyrus G14 (2022) */
|
|
.callback = lps0_prefer_microsoft,
|
|
.matches = {
|
|
DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."),
|
|
DMI_MATCH(DMI_PRODUCT_NAME, "ROG Zephyrus G14 GA402"),
|
|
},
|
|
},
|
|
{
|
|
/*
|
|
* Lenovo Yoga Slim 7 Pro X 14ARH7
|
|
* https://bugzilla.kernel.org/show_bug.cgi?id=216473 : 82V2
|
|
* https://bugzilla.kernel.org/show_bug.cgi?id=216438 : 82TL
|
|
*/
|
|
.callback = lps0_prefer_microsoft,
|
|
.matches = {
|
|
DMI_MATCH(DMI_BOARD_VENDOR, "LENOVO"),
|
|
DMI_MATCH(DMI_PRODUCT_NAME, "82"),
|
|
},
|
|
},
|
|
{}
|
|
};
|
|
|
|
static int lps0_device_attach(struct acpi_device *adev,
|
|
const struct acpi_device_id *not_used)
|
|
{
|
|
if (lps0_device_handle)
|
|
return 0;
|
|
|
|
lps0_dsm_func_mask_microsoft = validate_dsm(adev->handle,
|
|
ACPI_LPS0_DSM_UUID_MICROSOFT, 0,
|
|
&lps0_dsm_guid_microsoft);
|
|
if (acpi_s2idle_vendor_amd()) {
|
|
static const struct acpi_device_id *dev_id;
|
|
const struct amd_lps0_hid_device_data *data;
|
|
|
|
for (dev_id = &amd_hid_ids[0]; dev_id->id[0]; dev_id++)
|
|
if (acpi_dev_hid_uid_match(adev, dev_id->id, NULL))
|
|
break;
|
|
if (dev_id)
|
|
data = (const struct amd_lps0_hid_device_data *) dev_id->driver_data;
|
|
else
|
|
data = &amd_rembrandt;
|
|
rev_id = data->rev_id;
|
|
lps0_dsm_func_mask = validate_dsm(adev->handle,
|
|
ACPI_LPS0_DSM_UUID_AMD, rev_id, &lps0_dsm_guid);
|
|
if (lps0_dsm_func_mask > 0x3 && data->check_off_by_one) {
|
|
lps0_dsm_func_mask = (lps0_dsm_func_mask << 1) | 0x1;
|
|
acpi_handle_debug(adev->handle, "_DSM UUID %s: Adjusted function mask: 0x%x\n",
|
|
ACPI_LPS0_DSM_UUID_AMD, lps0_dsm_func_mask);
|
|
} else if (lps0_dsm_func_mask_microsoft > 0 && data->prefer_amd_guid &&
|
|
!prefer_microsoft_dsm_guid) {
|
|
lps0_dsm_func_mask_microsoft = -EINVAL;
|
|
acpi_handle_debug(adev->handle, "_DSM Using AMD method\n");
|
|
}
|
|
} else {
|
|
rev_id = 1;
|
|
lps0_dsm_func_mask = validate_dsm(adev->handle,
|
|
ACPI_LPS0_DSM_UUID, rev_id, &lps0_dsm_guid);
|
|
if (!prefer_microsoft_dsm_guid)
|
|
lps0_dsm_func_mask_microsoft = -EINVAL;
|
|
}
|
|
|
|
if (lps0_dsm_func_mask < 0 && lps0_dsm_func_mask_microsoft < 0)
|
|
return 0; //function evaluation failed
|
|
|
|
lps0_device_handle = adev->handle;
|
|
|
|
if (acpi_s2idle_vendor_amd())
|
|
lpi_device_get_constraints_amd();
|
|
else
|
|
lpi_device_get_constraints();
|
|
|
|
/*
|
|
* Use suspend-to-idle by default if ACPI_FADT_LOW_POWER_S0 is set in
|
|
* the FADT and the default suspend mode was not set from the command
|
|
* line.
|
|
*/
|
|
if ((acpi_gbl_FADT.flags & ACPI_FADT_LOW_POWER_S0) &&
|
|
mem_sleep_default > PM_SUSPEND_MEM && !acpi_sleep_default_s3) {
|
|
mem_sleep_current = PM_SUSPEND_TO_IDLE;
|
|
pr_info("Low-power S0 idle used by default for system suspend\n");
|
|
}
|
|
|
|
/*
|
|
* Some LPS0 systems, like ASUS Zenbook UX430UNR/i7-8550U, require the
|
|
* EC GPE to be enabled while suspended for certain wakeup devices to
|
|
* work, so mark it as wakeup-capable.
|
|
*/
|
|
acpi_ec_mark_gpe_for_wake();
|
|
|
|
return 0;
|
|
}
|
|
|
|
static struct acpi_scan_handler lps0_handler = {
|
|
.ids = lps0_device_ids,
|
|
.attach = lps0_device_attach,
|
|
};
|
|
|
|
int acpi_s2idle_prepare_late(void)
|
|
{
|
|
struct acpi_s2idle_dev_ops *handler;
|
|
|
|
if (!lps0_device_handle || sleep_no_lps0)
|
|
return 0;
|
|
|
|
if (pm_debug_messages_on)
|
|
lpi_check_constraints();
|
|
|
|
/* Screen off */
|
|
if (lps0_dsm_func_mask > 0)
|
|
acpi_sleep_run_lps0_dsm(acpi_s2idle_vendor_amd() ?
|
|
ACPI_LPS0_SCREEN_OFF_AMD :
|
|
ACPI_LPS0_SCREEN_OFF,
|
|
lps0_dsm_func_mask, lps0_dsm_guid);
|
|
|
|
if (lps0_dsm_func_mask_microsoft > 0)
|
|
acpi_sleep_run_lps0_dsm(ACPI_LPS0_SCREEN_OFF,
|
|
lps0_dsm_func_mask_microsoft, lps0_dsm_guid_microsoft);
|
|
|
|
/* LPS0 entry */
|
|
if (lps0_dsm_func_mask > 0)
|
|
acpi_sleep_run_lps0_dsm(acpi_s2idle_vendor_amd() ?
|
|
ACPI_LPS0_ENTRY_AMD :
|
|
ACPI_LPS0_ENTRY,
|
|
lps0_dsm_func_mask, lps0_dsm_guid);
|
|
if (lps0_dsm_func_mask_microsoft > 0) {
|
|
acpi_sleep_run_lps0_dsm(ACPI_LPS0_ENTRY,
|
|
lps0_dsm_func_mask_microsoft, lps0_dsm_guid_microsoft);
|
|
/* modern standby entry */
|
|
acpi_sleep_run_lps0_dsm(ACPI_LPS0_MS_ENTRY,
|
|
lps0_dsm_func_mask_microsoft, lps0_dsm_guid_microsoft);
|
|
}
|
|
|
|
list_for_each_entry(handler, &lps0_s2idle_devops_head, list_node) {
|
|
if (handler->prepare)
|
|
handler->prepare();
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void acpi_s2idle_restore_early(void)
|
|
{
|
|
struct acpi_s2idle_dev_ops *handler;
|
|
|
|
if (!lps0_device_handle || sleep_no_lps0)
|
|
return;
|
|
|
|
list_for_each_entry(handler, &lps0_s2idle_devops_head, list_node)
|
|
if (handler->restore)
|
|
handler->restore();
|
|
|
|
/* Modern standby exit */
|
|
if (lps0_dsm_func_mask_microsoft > 0)
|
|
acpi_sleep_run_lps0_dsm(ACPI_LPS0_MS_EXIT,
|
|
lps0_dsm_func_mask_microsoft, lps0_dsm_guid_microsoft);
|
|
|
|
/* LPS0 exit */
|
|
if (lps0_dsm_func_mask > 0)
|
|
acpi_sleep_run_lps0_dsm(acpi_s2idle_vendor_amd() ?
|
|
ACPI_LPS0_EXIT_AMD :
|
|
ACPI_LPS0_EXIT,
|
|
lps0_dsm_func_mask, lps0_dsm_guid);
|
|
if (lps0_dsm_func_mask_microsoft > 0)
|
|
acpi_sleep_run_lps0_dsm(ACPI_LPS0_EXIT,
|
|
lps0_dsm_func_mask_microsoft, lps0_dsm_guid_microsoft);
|
|
|
|
/* Screen on */
|
|
if (lps0_dsm_func_mask_microsoft > 0)
|
|
acpi_sleep_run_lps0_dsm(ACPI_LPS0_SCREEN_ON,
|
|
lps0_dsm_func_mask_microsoft, lps0_dsm_guid_microsoft);
|
|
if (lps0_dsm_func_mask > 0)
|
|
acpi_sleep_run_lps0_dsm(acpi_s2idle_vendor_amd() ?
|
|
ACPI_LPS0_SCREEN_ON_AMD :
|
|
ACPI_LPS0_SCREEN_ON,
|
|
lps0_dsm_func_mask, lps0_dsm_guid);
|
|
}
|
|
|
|
static const struct platform_s2idle_ops acpi_s2idle_ops_lps0 = {
|
|
.begin = acpi_s2idle_begin,
|
|
.prepare = acpi_s2idle_prepare,
|
|
.prepare_late = acpi_s2idle_prepare_late,
|
|
.wake = acpi_s2idle_wake,
|
|
.restore_early = acpi_s2idle_restore_early,
|
|
.restore = acpi_s2idle_restore,
|
|
.end = acpi_s2idle_end,
|
|
};
|
|
|
|
void __init acpi_s2idle_setup(void)
|
|
{
|
|
dmi_check_system(s2idle_dmi_table);
|
|
acpi_scan_add_handler(&lps0_handler);
|
|
s2idle_set_ops(&acpi_s2idle_ops_lps0);
|
|
}
|
|
|
|
int acpi_register_lps0_dev(struct acpi_s2idle_dev_ops *arg)
|
|
{
|
|
if (!lps0_device_handle || sleep_no_lps0)
|
|
return -ENODEV;
|
|
|
|
lock_system_sleep();
|
|
list_add(&arg->list_node, &lps0_s2idle_devops_head);
|
|
unlock_system_sleep();
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL_GPL(acpi_register_lps0_dev);
|
|
|
|
void acpi_unregister_lps0_dev(struct acpi_s2idle_dev_ops *arg)
|
|
{
|
|
if (!lps0_device_handle || sleep_no_lps0)
|
|
return;
|
|
|
|
lock_system_sleep();
|
|
list_del(&arg->list_node);
|
|
unlock_system_sleep();
|
|
}
|
|
EXPORT_SYMBOL_GPL(acpi_unregister_lps0_dev);
|
|
|
|
#endif /* CONFIG_SUSPEND */
|