diff options
Diffstat (limited to 'drivers/acpi/power.c')
| -rw-r--r-- | drivers/acpi/power.c | 116 |
1 files changed, 110 insertions, 6 deletions
diff --git a/drivers/acpi/power.c b/drivers/acpi/power.c index 23507d29f000..361a7721a6a8 100644 --- a/drivers/acpi/power.c +++ b/drivers/acpi/power.c @@ -23,11 +23,14 @@ #define pr_fmt(fmt) "ACPI: PM: " fmt +#include <linux/delay.h> +#include <linux/dmi.h> #include <linux/kernel.h> #include <linux/module.h> #include <linux/init.h> #include <linux/types.h> #include <linux/slab.h> +#include <linux/string_choices.h> #include <linux/pm_runtime.h> #include <linux/sysfs.h> #include <linux/acpi.h> @@ -61,6 +64,9 @@ struct acpi_power_resource_entry { struct acpi_power_resource *resource; }; +static bool hp_eb_gp12pxp_quirk; +static bool unused_power_resources_quirk; + static LIST_HEAD(acpi_power_resource_list); static DEFINE_MUTEX(power_resource_list_lock); @@ -196,7 +202,7 @@ static int __get_state(acpi_handle handle, u8 *state) cur_state = sta & ACPI_POWER_RESOURCE_STATE_ON; acpi_handle_debug(handle, "Power resource is %s\n", - cur_state ? "on" : "off"); + str_on_off(cur_state)); *state = cur_state; return 0; @@ -239,7 +245,7 @@ static int acpi_power_get_list_state(struct list_head *list, u8 *state) break; } - pr_debug("Power resource list is %s\n", cur_state ? "on" : "off"); + pr_debug("Power resource list is %s\n", str_on_off(cur_state)); *state = cur_state; return 0; @@ -949,8 +955,8 @@ struct acpi_device *acpi_add_power_resource(acpi_handle handle) mutex_init(&resource->resource_lock); INIT_LIST_HEAD(&resource->list_node); INIT_LIST_HEAD(&resource->dependents); - strcpy(acpi_device_name(device), ACPI_POWER_DEVICE_NAME); - strcpy(acpi_device_class(device), ACPI_POWER_CLASS); + strscpy(acpi_device_name(device), ACPI_POWER_DEVICE_NAME); + strscpy(acpi_device_class(device), ACPI_POWER_CLASS); device->power.state = ACPI_STATE_UNKNOWN; device->flags.match_driver = true; @@ -990,6 +996,38 @@ struct acpi_device *acpi_add_power_resource(acpi_handle handle) } #ifdef CONFIG_ACPI_SLEEP +static bool resource_is_gp12pxp(acpi_handle handle) +{ + const char *path; + bool ret; + + path = acpi_handle_path(handle); + ret = path && strcmp(path, "\\_SB_.PCI0.GP12.PXP_") == 0; + kfree(path); + + return ret; +} + +static void acpi_resume_on_eb_gp12pxp(struct acpi_power_resource *resource) +{ + acpi_handle_notice(resource->device.handle, + "HP EB quirk - turning OFF then ON\n"); + + __acpi_power_off(resource); + __acpi_power_on(resource); + + /* + * Use the same delay as DSDT uses in modem _RST method. + * + * Otherwise we get "Unable to change power state from unknown to D0, + * device inaccessible" error for the modem PCI device after thaw. + * + * This power resource is normally being enabled only during thaw (once) + * so this wait is not a performance issue. + */ + msleep(200); +} + void acpi_resume_power_resources(void) { struct acpi_power_resource *resource; @@ -1011,8 +1049,14 @@ void acpi_resume_power_resources(void) if (state == ACPI_POWER_RESOURCE_STATE_OFF && resource->ref_count) { - acpi_handle_debug(resource->device.handle, "Turning ON\n"); - __acpi_power_on(resource); + if (hp_eb_gp12pxp_quirk && + resource_is_gp12pxp(resource->device.handle)) { + acpi_resume_on_eb_gp12pxp(resource); + } else { + acpi_handle_debug(resource->device.handle, + "Turning ON\n"); + __acpi_power_on(resource); + } } mutex_unlock(&resource->resource_lock); @@ -1022,6 +1066,56 @@ void acpi_resume_power_resources(void) } #endif +static const struct dmi_system_id dmi_hp_elitebook_gp12pxp_quirk[] = { +/* + * This laptop (and possibly similar models too) has power resource called + * "GP12.PXP_" for its WWAN modem. + * + * For this power resource to turn ON power for the modem it needs certain + * internal flag called "ONEN" to be set. + * This flag only gets set from this power resource "_OFF" method, while the + * actual modem power gets turned off during suspend by "GP12.PTS" method + * called from the global "_PTS" (Prepare To Sleep) method. + * On the other hand, this power resource "_OFF" method implementation just + * sets the aforementioned flag without actually doing anything else (it + * doesn't contain any code to actually turn off power). + * + * The above means that when upon hibernation finish we try to set this + * power resource back ON since its "_STA" method returns 0 (while the resource + * is still considered in use) its "_ON" method won't do anything since + * that "ONEN" flag is not set. + * Overall, this means the modem is dead until laptop is rebooted since its + * power has been cut by "_PTS" and its PCI configuration was lost and not able + * to be restored. + * + * The easiest way to workaround the issue is to call this power resource + * "_OFF" method before calling the "_ON" method to make sure the "ONEN" + * flag gets properly set. + */ + { + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "HP"), + DMI_MATCH(DMI_PRODUCT_NAME, "HP EliteBook 855 G7 Notebook PC"), + }, + }, + {} +}; + +static const struct dmi_system_id dmi_leave_unused_power_resources_on[] = { + { + /* + * The Toshiba Click Mini has a CPR3 power-resource which must + * be on for the touchscreen to work, but which is not in any + * _PR? lists. The other 2 affected power-resources are no-ops. + */ + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "TOSHIBA"), + DMI_MATCH(DMI_PRODUCT_NAME, "SATELLITE Click Mini L9W-B"), + }, + }, + {} +}; + /** * acpi_turn_off_unused_power_resources - Turn off power resources not in use. */ @@ -1029,6 +1123,9 @@ void acpi_turn_off_unused_power_resources(void) { struct acpi_power_resource *resource; + if (unused_power_resources_quirk) + return; + mutex_lock(&power_resource_list_lock); list_for_each_entry_reverse(resource, &acpi_power_resource_list, list_node) { @@ -1045,3 +1142,10 @@ void acpi_turn_off_unused_power_resources(void) mutex_unlock(&power_resource_list_lock); } + +void __init acpi_power_resources_init(void) +{ + hp_eb_gp12pxp_quirk = dmi_check_system(dmi_hp_elitebook_gp12pxp_quirk); + unused_power_resources_quirk = + dmi_check_system(dmi_leave_unused_power_resources_on); +} |
