| /* |
| * sleep.c - ACPI sleep support. |
| * |
| * Copyright (c) 2004 David Shaohua Li <shaohua.li@intel.com> |
| * Copyright (c) 2000-2003 Patrick Mochel |
| * Copyright (c) 2003 Open Source Development Lab |
| * |
| * This file is released under the GPLv2. |
| * |
| */ |
| |
| #include <linux/delay.h> |
| #include <linux/irq.h> |
| #include <linux/dmi.h> |
| #include <linux/device.h> |
| #include <linux/suspend.h> |
| #include <asm/io.h> |
| #include <acpi/acpi_bus.h> |
| #include <acpi/acpi_drivers.h> |
| #include "sleep.h" |
| |
| u8 sleep_states[ACPI_S_STATE_COUNT]; |
| |
| static struct pm_ops acpi_pm_ops; |
| |
| extern void do_suspend_lowlevel_s4bios(void); |
| extern void do_suspend_lowlevel(void); |
| |
| 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, |
| }; |
| |
| static int init_8259A_after_S1; |
| |
| /** |
| * acpi_pm_prepare - Do preliminary suspend work. |
| * @pm_state: suspend state we're entering. |
| * |
| * 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. |
| */ |
| |
| static int acpi_pm_prepare(suspend_state_t pm_state) |
| { |
| u32 acpi_state = acpi_suspend_states[pm_state]; |
| |
| if (!sleep_states[acpi_state]) |
| return -EPERM; |
| |
| /* do we have a wakeup address for S2 and S3? */ |
| /* Here, we support only S4BIOS, those we set the wakeup address */ |
| /* S4OS is only supported for now via swsusp.. */ |
| if (pm_state == PM_SUSPEND_MEM || pm_state == PM_SUSPEND_DISK) { |
| if (!acpi_wakeup_address) |
| return -EFAULT; |
| acpi_set_firmware_waking_vector( |
| (acpi_physical_address) virt_to_phys( |
| (void *)acpi_wakeup_address)); |
| } |
| ACPI_FLUSH_CPU_CACHE(); |
| acpi_enable_wakeup_device_prep(acpi_state); |
| acpi_enter_sleep_state_prep(acpi_state); |
| return 0; |
| } |
| |
| |
| /** |
| * acpi_pm_enter - Actually enter a sleep state. |
| * @pm_state: State we're entering. |
| * |
| * Flush caches and go to sleep. For STR or STD, 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. |
| */ |
| |
| 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]; |
| |
| ACPI_FLUSH_CPU_CACHE(); |
| |
| /* Do arch specific saving of state. */ |
| if (pm_state > PM_SUSPEND_STANDBY) { |
| int error = acpi_save_state_mem(); |
| if (error) |
| return error; |
| } |
| |
| |
| local_irq_save(flags); |
| acpi_enable_wakeup_device(acpi_state); |
| switch (pm_state) |
| { |
| case PM_SUSPEND_STANDBY: |
| barrier(); |
| status = acpi_enter_sleep_state(acpi_state); |
| break; |
| |
| case PM_SUSPEND_MEM: |
| 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); |
| else |
| do_suspend_lowlevel_s4bios(); |
| break; |
| default: |
| return -EINVAL; |
| } |
| 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) |
| acpi_restore_state_mem(); |
| |
| |
| return ACPI_SUCCESS(status) ? 0 : -EFAULT; |
| } |
| |
| |
| /** |
| * acpi_pm_finish - Finish up suspend sequence. |
| * @pm_state: State we're coming out of. |
| * |
| * This is called after we wake back up (or if entering the sleep state |
| * failed). |
| */ |
| |
| static int acpi_pm_finish(suspend_state_t pm_state) |
| { |
| u32 acpi_state = acpi_suspend_states[pm_state]; |
| |
| acpi_leave_sleep_state(acpi_state); |
| acpi_disable_wakeup_device(acpi_state); |
| |
| /* 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); |
| } |
| return 0; |
| } |
| |
| |
| int acpi_suspend(u32 acpi_state) |
| { |
| suspend_state_t states[] = { |
| [1] = PM_SUSPEND_STANDBY, |
| [3] = PM_SUSPEND_MEM, |
| [4] = PM_SUSPEND_DISK, |
| }; |
| |
| if (acpi_state <= 4 && states[acpi_state]) |
| return pm_suspend(states[acpi_state]); |
| return -EINVAL; |
| } |
| |
| static struct pm_ops acpi_pm_ops = { |
| .prepare = acpi_pm_prepare, |
| .enter = acpi_pm_enter, |
| .finish = acpi_pm_finish, |
| }; |
| |
| |
| /* |
| * Toshiba fails to preserve interrupts over S1, reinitialization |
| * of 8259 is needed after S1 resume. |
| */ |
| static int __init init_ints_after_s1(struct dmi_system_id *d) |
| { |
| printk(KERN_WARNING "%s with broken S1 detected.\n", d->ident); |
| init_8259A_after_S1 = 1; |
| return 0; |
| } |
| |
| static struct dmi_system_id __initdata acpisleep_dmi_table[] = { |
| { |
| .callback = init_ints_after_s1, |
| .ident = "Toshiba Satellite 4030cdt", |
| .matches = { DMI_MATCH(DMI_PRODUCT_NAME, "S4030CDT/4.3"), }, |
| }, |
| { }, |
| }; |
| |
| static int __init acpi_sleep_init(void) |
| { |
| int i = 0; |
| |
| dmi_check_system(acpisleep_dmi_table); |
| |
| if (acpi_disabled) |
| return 0; |
| |
| printk(KERN_INFO PREFIX "(supports"); |
| for (i=0; i < ACPI_S_STATE_COUNT; i++) { |
| acpi_status status; |
| u8 type_a, type_b; |
| status = acpi_get_sleep_type_data(i, &type_a, &type_b); |
| if (ACPI_SUCCESS(status)) { |
| sleep_states[i] = 1; |
| printk(" S%d", i); |
| } |
| if (i == ACPI_STATE_S4) { |
| if (acpi_gbl_FACS->S4bios_f) { |
| sleep_states[i] = 1; |
| printk(" S4bios"); |
| acpi_pm_ops.pm_disk_mode = PM_DISK_FIRMWARE; |
| } |
| if (sleep_states[i]) |
| acpi_pm_ops.pm_disk_mode = PM_DISK_PLATFORM; |
| } |
| } |
| printk(")\n"); |
| |
| pm_set_ops(&acpi_pm_ops); |
| return 0; |
| } |
| |
| late_initcall(acpi_sleep_init); |