X-Git-Url: https://err.no/cgi-bin/gitweb.cgi?a=blobdiff_plain;f=drivers%2Facpi%2Fsleep%2Fmain.c;h=55eca6eabcd8e2b38187ada6f123eecb36f250cb;hb=fd4aff1a28eecbd729b409bf7d3eff5948f20414;hp=62ce87d7165101078aa829b73294dd6922e1519a;hpb=b20e481ab595e9667c33e2393bdfe9a31870d11f;p=linux-2.6 diff --git a/drivers/acpi/sleep/main.c b/drivers/acpi/sleep/main.c index 62ce87d716..55eca6eabc 100644 --- a/drivers/acpi/sleep/main.c +++ b/drivers/acpi/sleep/main.c @@ -29,40 +29,59 @@ static u32 acpi_suspend_states[] = { [PM_SUSPEND_ON] = ACPI_STATE_S0, [PM_SUSPEND_STANDBY] = ACPI_STATE_S1, [PM_SUSPEND_MEM] = ACPI_STATE_S3, - [PM_SUSPEND_DISK] = ACPI_STATE_S4, [PM_SUSPEND_MAX] = ACPI_STATE_S5 }; static int init_8259A_after_S1; +extern int acpi_sleep_prepare(u32 acpi_state); +extern void acpi_power_off(void); + +static u32 acpi_target_sleep_state = ACPI_STATE_S0; + +/** + * acpi_pm_set_target - Set the target system sleep state to the state + * associated with given @pm_state, if supported. + */ + +static int acpi_pm_set_target(suspend_state_t pm_state) +{ + u32 acpi_state = acpi_suspend_states[pm_state]; + int error = 0; + + if (sleep_states[acpi_state]) { + acpi_target_sleep_state = acpi_state; + } else { + printk(KERN_ERR "ACPI does not support this state: %d\n", + pm_state); + error = -ENOSYS; + } + return error; +} + /** * acpi_pm_prepare - Do preliminary suspend work. - * @pm_state: suspend state we're entering. + * @pm_state: ignored * - * Make sure we support the state. If we do, and we need it, set the - * firmware waking vector and do arch-specific nastiness to get the - * wakeup code to the waking vector. + * If necessary, set the firmware waking vector and do arch-specific + * nastiness to get the wakeup code to the waking vector. */ -extern int acpi_sleep_prepare(u32 acpi_state); -extern void acpi_power_off(void); - static int acpi_pm_prepare(suspend_state_t pm_state) { - u32 acpi_state = acpi_suspend_states[pm_state]; + int error = acpi_sleep_prepare(acpi_target_sleep_state); - if (!sleep_states[acpi_state]) { - printk("acpi_pm_prepare does not support %d \n", pm_state); - return -EPERM; - } - return acpi_sleep_prepare(acpi_state); + if (error) + acpi_target_sleep_state = ACPI_STATE_S0; + + return error; } /** * acpi_pm_enter - Actually enter a sleep state. - * @pm_state: State we're entering. + * @pm_state: ignored * - * Flush caches and go to sleep. For STR or STD, we have to call + * Flush caches and go to sleep. For STR or S2, we have to call * arch-specific assembly, which in turn call acpi_enter_sleep_state(). * It's unfortunate, but it works. Please fix if you're feeling frisky. */ @@ -71,39 +90,32 @@ static int acpi_pm_enter(suspend_state_t pm_state) { acpi_status status = AE_OK; unsigned long flags = 0; - u32 acpi_state = acpi_suspend_states[pm_state]; + u32 acpi_state = acpi_target_sleep_state; ACPI_FLUSH_CPU_CACHE(); /* Do arch specific saving of state. */ - if (pm_state > PM_SUSPEND_STANDBY) { + if (acpi_state == ACPI_STATE_S2 || acpi_state == ACPI_STATE_S3) { int error = acpi_save_state_mem(); - if (error) + + if (error) { + acpi_target_sleep_state = ACPI_STATE_S0; return error; + } } local_irq_save(flags); acpi_enable_wakeup_device(acpi_state); - switch (pm_state) { - case PM_SUSPEND_STANDBY: + switch (acpi_state) { + case ACPI_STATE_S1: barrier(); status = acpi_enter_sleep_state(acpi_state); break; - case PM_SUSPEND_MEM: + case ACPI_STATE_S2: + case ACPI_STATE_S3: do_suspend_lowlevel(); break; - - case PM_SUSPEND_DISK: - if (acpi_pm_ops.pm_disk_mode == PM_DISK_PLATFORM) - status = acpi_enter_sleep_state(acpi_state); - break; - case PM_SUSPEND_MAX: - acpi_power_off(); - break; - - default: - return -EINVAL; } /* ACPI 3.0 specs (P62) says that it's the responsabilty @@ -116,12 +128,8 @@ static int acpi_pm_enter(suspend_state_t pm_state) local_irq_restore(flags); printk(KERN_DEBUG "Back to C!\n"); - /* restore processor state - * We should only be here if we're coming back from STR or STD. - * And, in the case of the latter, the memory image should have already - * been loaded from disk. - */ - if (pm_state > PM_SUSPEND_STANDBY) + /* restore processor state */ + if (acpi_state == ACPI_STATE_S2 || acpi_state == ACPI_STATE_S3) acpi_restore_state_mem(); return ACPI_SUCCESS(status) ? 0 : -EFAULT; @@ -129,7 +137,7 @@ static int acpi_pm_enter(suspend_state_t pm_state) /** * acpi_pm_finish - Finish up suspend sequence. - * @pm_state: State we're coming out of. + * @pm_state: ignored * * This is called after we wake back up (or if entering the sleep state * failed). @@ -137,7 +145,7 @@ static int acpi_pm_enter(suspend_state_t pm_state) static int acpi_pm_finish(suspend_state_t pm_state) { - u32 acpi_state = acpi_suspend_states[pm_state]; + u32 acpi_state = acpi_target_sleep_state; acpi_leave_sleep_state(acpi_state); acpi_disable_wakeup_device(acpi_state); @@ -145,6 +153,8 @@ static int acpi_pm_finish(suspend_state_t pm_state) /* reset firmware waking vector */ acpi_set_firmware_waking_vector((acpi_physical_address) 0); + acpi_target_sleep_state = ACPI_STATE_S0; + if (init_8259A_after_S1) { printk("Broken toshiba laptop -> kicking interrupts\n"); init_8259A(0); @@ -157,29 +167,174 @@ int acpi_suspend(u32 acpi_state) suspend_state_t states[] = { [1] = PM_SUSPEND_STANDBY, [3] = PM_SUSPEND_MEM, - [4] = PM_SUSPEND_DISK, [5] = PM_SUSPEND_MAX }; if (acpi_state < 6 && states[acpi_state]) return pm_suspend(states[acpi_state]); + if (acpi_state == 4) + return hibernate(); return -EINVAL; } static int acpi_pm_state_valid(suspend_state_t pm_state) { - u32 acpi_state = acpi_suspend_states[pm_state]; + u32 acpi_state; + + switch (pm_state) { + case PM_SUSPEND_ON: + case PM_SUSPEND_STANDBY: + case PM_SUSPEND_MEM: + acpi_state = acpi_suspend_states[pm_state]; - return sleep_states[acpi_state]; + return sleep_states[acpi_state]; + default: + return 0; + } } static struct pm_ops acpi_pm_ops = { .valid = acpi_pm_state_valid, + .set_target = acpi_pm_set_target, .prepare = acpi_pm_prepare, .enter = acpi_pm_enter, .finish = acpi_pm_finish, }; +#ifdef CONFIG_SOFTWARE_SUSPEND +static int acpi_hibernation_prepare(void) +{ + return acpi_sleep_prepare(ACPI_STATE_S4); +} + +static int acpi_hibernation_enter(void) +{ + acpi_status status = AE_OK; + unsigned long flags = 0; + + ACPI_FLUSH_CPU_CACHE(); + + local_irq_save(flags); + acpi_enable_wakeup_device(ACPI_STATE_S4); + /* This shouldn't return. If it returns, we have a problem */ + status = acpi_enter_sleep_state(ACPI_STATE_S4); + local_irq_restore(flags); + + return ACPI_SUCCESS(status) ? 0 : -EFAULT; +} + +static void acpi_hibernation_finish(void) +{ + acpi_leave_sleep_state(ACPI_STATE_S4); + acpi_disable_wakeup_device(ACPI_STATE_S4); + + /* reset firmware waking vector */ + acpi_set_firmware_waking_vector((acpi_physical_address) 0); + + if (init_8259A_after_S1) { + printk("Broken toshiba laptop -> kicking interrupts\n"); + init_8259A(0); + } +} + +static int acpi_hibernation_pre_restore(void) +{ + acpi_status status; + + status = acpi_hw_disable_all_gpes(); + + return ACPI_SUCCESS(status) ? 0 : -EFAULT; +} + +static void acpi_hibernation_restore_cleanup(void) +{ + acpi_hw_enable_all_runtime_gpes(); +} + +static struct hibernation_ops acpi_hibernation_ops = { + .prepare = acpi_hibernation_prepare, + .enter = acpi_hibernation_enter, + .finish = acpi_hibernation_finish, + .pre_restore = acpi_hibernation_pre_restore, + .restore_cleanup = acpi_hibernation_restore_cleanup, +}; +#endif /* CONFIG_SOFTWARE_SUSPEND */ + +/** + * acpi_pm_device_sleep_state - return preferred power state of ACPI device + * in the system sleep state given by %acpi_target_sleep_state + * @dev: device to examine + * @wake: if set, the device should be able to wake up the system + * @d_min_p: used to store the upper limit of allowed states range + * Return value: preferred power state of the device on success, -ENODEV on + * failure (ie. if there's no 'struct acpi_device' for @dev) + * + * Find the lowest power (highest number) ACPI device power state that + * device @dev can be in while the system is in the sleep state represented + * by %acpi_target_sleep_state. If @wake is nonzero, the device should be + * able to wake up the system from this sleep state. If @d_min_p is set, + * the highest power (lowest number) device power state of @dev allowed + * in this system sleep state is stored at the location pointed to by it. + * + * The caller must ensure that @dev is valid before using this function. + * The caller is also responsible for figuring out if the device is + * supposed to be able to wake up the system and passing this information + * via @wake. + */ + +int acpi_pm_device_sleep_state(struct device *dev, int wake, int *d_min_p) +{ + acpi_handle handle = DEVICE_ACPI_HANDLE(dev); + struct acpi_device *adev; + char acpi_method[] = "_SxD"; + unsigned long d_min, d_max; + + if (!handle || ACPI_FAILURE(acpi_bus_get_device(handle, &adev))) { + printk(KERN_ERR "ACPI handle has no context!\n"); + return -ENODEV; + } + + acpi_method[2] = '0' + acpi_target_sleep_state; + /* + * If the sleep state is S0, we will return D3, but if the device has + * _S0W, we will use the value from _S0W + */ + d_min = ACPI_STATE_D0; + d_max = ACPI_STATE_D3; + + /* + * If present, _SxD methods return the minimum D-state (highest power + * state) we can use for the corresponding S-states. Otherwise, the + * minimum D-state is D0 (ACPI 3.x). + * + * NOTE: We rely on acpi_evaluate_integer() not clobbering the integer + * provided -- that's our fault recovery, we ignore retval. + */ + if (acpi_target_sleep_state > ACPI_STATE_S0) + acpi_evaluate_integer(handle, acpi_method, NULL, &d_min); + + /* + * If _PRW says we can wake up the system from the target sleep state, + * the D-state returned by _SxD is sufficient for that (we assume a + * wakeup-aware driver if wake is set). Still, if _SxW exists + * (ACPI 3.x), it should return the maximum (lowest power) D-state that + * can wake the system. _S0W may be valid, too. + */ + if (acpi_target_sleep_state == ACPI_STATE_S0 || + (wake && adev->wakeup.state.enabled && + adev->wakeup.sleep_state <= acpi_target_sleep_state)) { + acpi_method[3] = 'W'; + acpi_evaluate_integer(handle, acpi_method, NULL, &d_max); + /* Sanity check */ + if (d_max < d_min) + d_min = d_max; + } + + if (d_min_p) + *d_min_p = d_min; + return d_max; +} + /* * Toshiba fails to preserve interrupts over S1, reinitialization * of 8259 is needed after S1 resume. @@ -200,7 +355,7 @@ static struct dmi_system_id __initdata acpisleep_dmi_table[] = { {}, }; -static int __init acpi_sleep_init(void) +int __init acpi_sleep_init(void) { int i = 0; @@ -218,15 +373,17 @@ static int __init acpi_sleep_init(void) sleep_states[i] = 1; printk(" S%d", i); } - if (i == ACPI_STATE_S4) { - if (sleep_states[i]) - acpi_pm_ops.pm_disk_mode = PM_DISK_PLATFORM; - } } printk(")\n"); pm_set_ops(&acpi_pm_ops); + +#ifdef CONFIG_SOFTWARE_SUSPEND + if (sleep_states[ACPI_STATE_S4]) + hibernation_set_ops(&acpi_hibernation_ops); +#else + sleep_states[ACPI_STATE_S4] = 0; +#endif + return 0; } - -late_initcall(acpi_sleep_init);