diff options
Diffstat (limited to 'drivers/acpi')
-rw-r--r-- | drivers/acpi/battery.c | 2 | ||||
-rw-r--r-- | drivers/acpi/button.c | 5 | ||||
-rw-r--r-- | drivers/acpi/device_pm.c | 102 | ||||
-rw-r--r-- | drivers/acpi/ec.c | 2 | ||||
-rw-r--r-- | drivers/acpi/internal.h | 4 | ||||
-rw-r--r-- | drivers/acpi/pci_root.c | 5 | ||||
-rw-r--r-- | drivers/acpi/proc.c | 4 | ||||
-rw-r--r-- | drivers/acpi/scan.c | 23 | ||||
-rw-r--r-- | drivers/acpi/sleep.c | 152 |
9 files changed, 210 insertions, 89 deletions
diff --git a/drivers/acpi/battery.c b/drivers/acpi/battery.c index d42eeef9d928..1cbb88d938e5 100644 --- a/drivers/acpi/battery.c +++ b/drivers/acpi/battery.c @@ -782,7 +782,7 @@ static int acpi_battery_update(struct acpi_battery *battery, bool resume) if ((battery->state & ACPI_BATTERY_STATE_CRITICAL) || (test_bit(ACPI_BATTERY_ALARM_PRESENT, &battery->flags) && (battery->capacity_now <= battery->alarm))) - pm_wakeup_event(&battery->device->dev, 0); + acpi_pm_wakeup_event(&battery->device->dev); return result; } diff --git a/drivers/acpi/button.c b/drivers/acpi/button.c index e19f530f1083..91cfdf377df7 100644 --- a/drivers/acpi/button.c +++ b/drivers/acpi/button.c @@ -217,7 +217,7 @@ static int acpi_lid_notify_state(struct acpi_device *device, int state) } if (state) - pm_wakeup_event(&device->dev, 0); + acpi_pm_wakeup_event(&device->dev); ret = blocking_notifier_call_chain(&acpi_lid_notifier, state, device); if (ret == NOTIFY_DONE) @@ -402,7 +402,7 @@ static void acpi_button_notify(struct acpi_device *device, u32 event) } else { int keycode; - pm_wakeup_event(&device->dev, 0); + acpi_pm_wakeup_event(&device->dev); if (button->suspended) break; @@ -534,6 +534,7 @@ static int acpi_button_add(struct acpi_device *device) lid_device = device; } + device_init_wakeup(&device->dev, true); printk(KERN_INFO PREFIX "%s [%s]\n", name, acpi_device_bid(device)); return 0; diff --git a/drivers/acpi/device_pm.c b/drivers/acpi/device_pm.c index 993fd31394c8..28938b5a334e 100644 --- a/drivers/acpi/device_pm.c +++ b/drivers/acpi/device_pm.c @@ -24,6 +24,7 @@ #include <linux/pm_qos.h> #include <linux/pm_domain.h> #include <linux/pm_runtime.h> +#include <linux/suspend.h> #include "internal.h" @@ -385,6 +386,12 @@ EXPORT_SYMBOL(acpi_bus_power_manageable); #ifdef CONFIG_PM static DEFINE_MUTEX(acpi_pm_notifier_lock); +void acpi_pm_wakeup_event(struct device *dev) +{ + pm_wakeup_dev_event(dev, 0, acpi_s2idle_wakeup()); +} +EXPORT_SYMBOL_GPL(acpi_pm_wakeup_event); + static void acpi_pm_notify_handler(acpi_handle handle, u32 val, void *not_used) { struct acpi_device *adev; @@ -399,9 +406,9 @@ static void acpi_pm_notify_handler(acpi_handle handle, u32 val, void *not_used) mutex_lock(&acpi_pm_notifier_lock); if (adev->wakeup.flags.notifier_present) { - __pm_wakeup_event(adev->wakeup.ws, 0); - if (adev->wakeup.context.work.func) - queue_pm_work(&adev->wakeup.context.work); + pm_wakeup_ws_event(adev->wakeup.ws, 0, acpi_s2idle_wakeup()); + if (adev->wakeup.context.func) + adev->wakeup.context.func(&adev->wakeup.context); } mutex_unlock(&acpi_pm_notifier_lock); @@ -413,7 +420,7 @@ static void acpi_pm_notify_handler(acpi_handle handle, u32 val, void *not_used) * acpi_add_pm_notifier - Register PM notify handler for given ACPI device. * @adev: ACPI device to add the notify handler for. * @dev: Device to generate a wakeup event for while handling the notification. - * @work_func: Work function to execute when handling the notification. + * @func: Work function to execute when handling the notification. * * NOTE: @adev need not be a run-wake or wakeup device to be a valid source of * PM wakeup events. For example, wakeup events may be generated for bridges @@ -421,11 +428,11 @@ static void acpi_pm_notify_handler(acpi_handle handle, u32 val, void *not_used) * bridge itself doesn't have a wakeup GPE associated with it. */ acpi_status acpi_add_pm_notifier(struct acpi_device *adev, struct device *dev, - void (*work_func)(struct work_struct *work)) + void (*func)(struct acpi_device_wakeup_context *context)) { acpi_status status = AE_ALREADY_EXISTS; - if (!dev && !work_func) + if (!dev && !func) return AE_BAD_PARAMETER; mutex_lock(&acpi_pm_notifier_lock); @@ -435,8 +442,7 @@ acpi_status acpi_add_pm_notifier(struct acpi_device *adev, struct device *dev, adev->wakeup.ws = wakeup_source_register(dev_name(&adev->dev)); adev->wakeup.context.dev = dev; - if (work_func) - INIT_WORK(&adev->wakeup.context.work, work_func); + adev->wakeup.context.func = func; status = acpi_install_notify_handler(adev->handle, ACPI_SYSTEM_NOTIFY, acpi_pm_notify_handler, NULL); @@ -469,10 +475,7 @@ acpi_status acpi_remove_pm_notifier(struct acpi_device *adev) if (ACPI_FAILURE(status)) goto out; - if (adev->wakeup.context.work.func) { - cancel_work_sync(&adev->wakeup.context.work); - adev->wakeup.context.work.func = NULL; - } + adev->wakeup.context.func = NULL; adev->wakeup.context.dev = NULL; wakeup_source_unregister(adev->wakeup.ws); @@ -493,6 +496,13 @@ bool acpi_bus_can_wakeup(acpi_handle handle) } EXPORT_SYMBOL(acpi_bus_can_wakeup); +bool acpi_pm_device_can_wakeup(struct device *dev) +{ + struct acpi_device *adev = ACPI_COMPANION(dev); + + return adev ? acpi_device_can_wakeup(adev) : false; +} + /** * acpi_dev_pm_get_state - Get preferred power state of ACPI device. * @dev: Device whose preferred target power state to return. @@ -658,16 +668,15 @@ EXPORT_SYMBOL(acpi_pm_device_sleep_state); /** * acpi_pm_notify_work_func - ACPI devices wakeup notification work function. - * @work: Work item to handle. + * @context: Device wakeup context. */ -static void acpi_pm_notify_work_func(struct work_struct *work) +static void acpi_pm_notify_work_func(struct acpi_device_wakeup_context *context) { - struct device *dev; + struct device *dev = context->dev; - dev = container_of(work, struct acpi_device_wakeup_context, work)->dev; if (dev) { pm_wakeup_event(dev, 0); - pm_runtime_resume(dev); + pm_request_resume(dev); } } @@ -693,80 +702,53 @@ static int acpi_device_wakeup(struct acpi_device *adev, u32 target_state, acpi_status res; int error; + if (adev->wakeup.flags.enabled) + return 0; + error = acpi_enable_wakeup_device_power(adev, target_state); if (error) return error; - if (adev->wakeup.flags.enabled) - return 0; - res = acpi_enable_gpe(wakeup->gpe_device, wakeup->gpe_number); - if (ACPI_SUCCESS(res)) { - adev->wakeup.flags.enabled = 1; - } else { + if (ACPI_FAILURE(res)) { acpi_disable_wakeup_device_power(adev); return -EIO; } - } else { - if (adev->wakeup.flags.enabled) { - acpi_disable_gpe(wakeup->gpe_device, wakeup->gpe_number); - adev->wakeup.flags.enabled = 0; - } + adev->wakeup.flags.enabled = 1; + } else if (adev->wakeup.flags.enabled) { + acpi_disable_gpe(wakeup->gpe_device, wakeup->gpe_number); acpi_disable_wakeup_device_power(adev); + adev->wakeup.flags.enabled = 0; } return 0; } /** - * acpi_pm_device_run_wake - Enable/disable remote wakeup for given device. - * @dev: Device to enable/disable the platform to wake up. + * acpi_pm_set_device_wakeup - Enable/disable remote wakeup for given device. + * @dev: Device to enable/disable to generate wakeup events. * @enable: Whether to enable or disable the wakeup functionality. */ -int acpi_pm_device_run_wake(struct device *phys_dev, bool enable) -{ - struct acpi_device *adev; - - if (!device_run_wake(phys_dev)) - return -EINVAL; - - adev = ACPI_COMPANION(phys_dev); - if (!adev) { - dev_dbg(phys_dev, "ACPI companion missing in %s!\n", __func__); - return -ENODEV; - } - - return acpi_device_wakeup(adev, ACPI_STATE_S0, enable); -} -EXPORT_SYMBOL(acpi_pm_device_run_wake); - -#ifdef CONFIG_PM_SLEEP -/** - * acpi_pm_device_sleep_wake - Enable or disable device to wake up the system. - * @dev: Device to enable/desible to wake up the system from sleep states. - * @enable: Whether to enable or disable @dev to wake up the system. - */ -int acpi_pm_device_sleep_wake(struct device *dev, bool enable) +int acpi_pm_set_device_wakeup(struct device *dev, bool enable) { struct acpi_device *adev; int error; - if (!device_can_wakeup(dev)) - return -EINVAL; - adev = ACPI_COMPANION(dev); if (!adev) { dev_dbg(dev, "ACPI companion missing in %s!\n", __func__); return -ENODEV; } + if (!acpi_device_can_wakeup(adev)) + return -EINVAL; + error = acpi_device_wakeup(adev, acpi_target_system_state(), enable); if (!error) - dev_info(dev, "System wakeup %s by ACPI\n", - enable ? "enabled" : "disabled"); + dev_dbg(dev, "Wakeup %s by ACPI\n", enable ? "enabled" : "disabled"); return error; } -#endif /* CONFIG_PM_SLEEP */ +EXPORT_SYMBOL(acpi_pm_set_device_wakeup); /** * acpi_dev_pm_low_power - Put ACPI device into a low-power state. 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) struct acpi_ec *ec = acpi_driver_data(to_acpi_device(dev)); - if (ec_freeze_events) + if (acpi_sleep_no_ec_events() && ec_freeze_events) acpi_ec_disable_event(ec); return 0; } diff --git a/drivers/acpi/internal.h b/drivers/acpi/internal.h index 66229ffa909b..be79f7db1850 100644 --- a/drivers/acpi/internal.h +++ b/drivers/acpi/internal.h @@ -198,8 +198,12 @@ void acpi_ec_remove_query_handler(struct acpi_ec *ec, u8 query_bit); Suspend/Resume -------------------------------------------------------------------------- */ #ifdef CONFIG_ACPI_SYSTEM_POWER_STATES_SUPPORT +extern bool acpi_s2idle_wakeup(void); +extern bool acpi_sleep_no_ec_events(void); extern int acpi_sleep_init(void); #else +static inline bool acpi_s2idle_wakeup(void) { return false; } +static inline bool acpi_sleep_no_ec_events(void) { return true; } static inline int acpi_sleep_init(void) { return -ENXIO; } #endif diff --git a/drivers/acpi/pci_root.c b/drivers/acpi/pci_root.c index 919be0aa2578..0d34e622a8b5 100644 --- a/drivers/acpi/pci_root.c +++ b/drivers/acpi/pci_root.c @@ -608,8 +608,7 @@ static int acpi_pci_root_add(struct acpi_device *device, pcie_no_aspm(); pci_acpi_add_bus_pm_notifier(device); - if (device->wakeup.flags.run_wake) - device_set_run_wake(root->bus->bridge, true); + device_set_wakeup_capable(root->bus->bridge, device->wakeup.flags.valid); if (hotadd) { pcibios_resource_survey_bus(root->bus); @@ -649,7 +648,7 @@ static void acpi_pci_root_remove(struct acpi_device *device) pci_stop_root_bus(root->bus); pci_ioapic_remove(root); - device_set_run_wake(root->bus->bridge, false); + device_set_wakeup_capable(root->bus->bridge, false); pci_acpi_remove_bus_pm_notifier(device); pci_remove_root_bus(root->bus); diff --git a/drivers/acpi/proc.c b/drivers/acpi/proc.c index a34669cc823b..85ac848ac6ab 100644 --- a/drivers/acpi/proc.c +++ b/drivers/acpi/proc.c @@ -42,7 +42,7 @@ acpi_system_wakeup_device_seq_show(struct seq_file *seq, void *offset) if (!dev->physical_node_count) { seq_printf(seq, "%c%-8s\n", - dev->wakeup.flags.run_wake ? '*' : ' ', + dev->wakeup.flags.valid ? '*' : ' ', device_may_wakeup(&dev->dev) ? "enabled" : "disabled"); } else { @@ -58,7 +58,7 @@ acpi_system_wakeup_device_seq_show(struct seq_file *seq, void *offset) seq_printf(seq, "\t\t"); seq_printf(seq, "%c%-8s %s:%s\n", - dev->wakeup.flags.run_wake ? '*' : ' ', + dev->wakeup.flags.valid ? '*' : ' ', (device_may_wakeup(&dev->dev) || device_may_wakeup(ldev)) ? "enabled" : "disabled", diff --git a/drivers/acpi/scan.c b/drivers/acpi/scan.c index d53162997f32..09f65f57bebe 100644 --- a/drivers/acpi/scan.c +++ b/drivers/acpi/scan.c @@ -835,7 +835,7 @@ static int acpi_bus_extract_wakeup_device_power_package(acpi_handle handle, return err; } -static void acpi_wakeup_gpe_init(struct acpi_device *device) +static bool acpi_wakeup_gpe_init(struct acpi_device *device) { static const struct acpi_device_id button_device_ids[] = { {"PNP0C0C", 0}, @@ -845,13 +845,11 @@ static void acpi_wakeup_gpe_init(struct acpi_device *device) }; struct acpi_device_wakeup *wakeup = &device->wakeup; acpi_status status; - acpi_event_status event_status; wakeup->flags.notifier_present = 0; /* Power button, Lid switch always enable wakeup */ if (!acpi_match_device_ids(device, button_device_ids)) { - wakeup->flags.run_wake = 1; if (!acpi_match_device_ids(device, &button_device_ids[1])) { /* Do not use Lid/sleep button for S5 wakeup */ if (wakeup->sleep_state == ACPI_STATE_S5) @@ -859,17 +857,12 @@ static void acpi_wakeup_gpe_init(struct acpi_device *device) } acpi_mark_gpe_for_wake(wakeup->gpe_device, wakeup->gpe_number); device_set_wakeup_capable(&device->dev, true); - return; + return true; } - acpi_setup_gpe_for_wake(device->handle, wakeup->gpe_device, - wakeup->gpe_number); - status = acpi_get_gpe_status(wakeup->gpe_device, wakeup->gpe_number, - &event_status); - if (ACPI_FAILURE(status)) - return; - - wakeup->flags.run_wake = !!(event_status & ACPI_EVENT_FLAG_HAS_HANDLER); + status = acpi_setup_gpe_for_wake(device->handle, wakeup->gpe_device, + wakeup->gpe_number); + return ACPI_SUCCESS(status); } static void acpi_bus_get_wakeup_device_flags(struct acpi_device *device) @@ -887,10 +880,10 @@ static void acpi_bus_get_wakeup_device_flags(struct acpi_device *device) return; } - device->wakeup.flags.valid = 1; + device->wakeup.flags.valid = acpi_wakeup_gpe_init(device); device->wakeup.prepare_count = 0; - acpi_wakeup_gpe_init(device); - /* Call _PSW/_DSW object to disable its ability to wake the sleeping + /* + * Call _PSW/_DSW object to disable its ability to wake the sleeping * system for the ACPI device with the _PRW object. * The _PSW object is depreciated in ACPI 3.0 and is replaced by _DSW. * So it is necessary to call _DSW object first. Only when it is not 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 = { .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; |