PM: Introduce PM_EVENT_HIBERNATE callback state

During the last step of hibernation in the "platform" mode (with the
help of ACPI) we use the suspend code, including the devices'
->suspend() methods, to prepare the system for entering the ACPI S4
system sleep state.

But at least for some devices the operations performed by the
->suspend() callback in that case must be different from its operations
during regular suspend.

For this reason, introduce the new PM event type PM_EVENT_HIBERNATE and
pass it to the device drivers' ->suspend() methods during the last phase
of hibernation, so that they can distinguish this case and handle it as
appropriate.  Modify the drivers that handle PM_EVENT_SUSPEND in a
special way and need to handle PM_EVENT_HIBERNATE in the same way.

These changes are necessary to fix a hibernation regression related
to the i915 driver (ref. http://lkml.org/lkml/2008/2/22/488).

Signed-off-by: Rafael J. Wysocki <rjw@sisk.pl>
Acked-by: Pavel Machek <pavel@ucw.cz>
Tested-by: Jeff Chua <jeff.chua.linux@gmail.com>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
This commit is contained in:
Rafael J. Wysocki 2008-02-23 19:13:25 +01:00 committed by Linus Torvalds
parent 39273b58a4
commit 3a2d5b7001
18 changed files with 42 additions and 24 deletions

View file

@ -310,9 +310,12 @@ used with suspend-to-disk:
PM_EVENT_SUSPEND -- quiesce the driver and put hardware into a low-power PM_EVENT_SUSPEND -- quiesce the driver and put hardware into a low-power
state. When used with system sleep states like "suspend-to-RAM" or state. When used with system sleep states like "suspend-to-RAM" or
"standby", the upcoming resume() call will often be able to rely on "standby", the upcoming resume() call will often be able to rely on
state kept in hardware, or issue system wakeup events. When used state kept in hardware, or issue system wakeup events.
instead with suspend-to-disk, few devices support this capability;
most are completely powered off. PM_EVENT_HIBERNATE -- Put hardware into a low-power state and enable wakeup
events as appropriate. It is only used with hibernation
(suspend-to-disk) and few devices are able to wake up the system from
this state; most are completely powered off.
PM_EVENT_FREEZE -- quiesce the driver, but don't necessarily change into PM_EVENT_FREEZE -- quiesce the driver, but don't necessarily change into
any low power mode. A system snapshot is about to be taken, often any low power mode. A system snapshot is about to be taken, often
@ -329,8 +332,8 @@ used with suspend-to-disk:
wakeup events nor DMA are allowed. wakeup events nor DMA are allowed.
To enter "standby" (ACPI S1) or "Suspend to RAM" (STR, ACPI S3) states, or To enter "standby" (ACPI S1) or "Suspend to RAM" (STR, ACPI S3) states, or
the similarly named APM states, only PM_EVENT_SUSPEND is used; for "Suspend the similarly named APM states, only PM_EVENT_SUSPEND is used; the other event
to Disk" (STD, hibernate, ACPI S4), all of those event codes are used. codes are used for hibernation ("Suspend to Disk", STD, ACPI S4).
There's also PM_EVENT_ON, a value which never appears as a suspend event There's also PM_EVENT_ON, a value which never appears as a suspend event
but is sometimes used to record the "not suspended" device state. but is sometimes used to record the "not suspended" device state.

View file

@ -1932,7 +1932,7 @@ static int ahci_pci_device_suspend(struct pci_dev *pdev, pm_message_t mesg)
void __iomem *mmio = host->iomap[AHCI_PCI_BAR]; void __iomem *mmio = host->iomap[AHCI_PCI_BAR];
u32 ctl; u32 ctl;
if (mesg.event == PM_EVENT_SUSPEND) { if (mesg.event & PM_EVENT_SLEEP) {
/* AHCI spec rev1.1 section 8.3.3: /* AHCI spec rev1.1 section 8.3.3:
* Software must disable interrupts prior to requesting a * Software must disable interrupts prior to requesting a
* transition of the HBA to D3 state. * transition of the HBA to D3 state.

View file

@ -1339,7 +1339,7 @@ static int piix_pci_device_suspend(struct pci_dev *pdev, pm_message_t mesg)
* cycles and power trying to do something to the sleeping * cycles and power trying to do something to the sleeping
* beauty. * beauty.
*/ */
if (piix_broken_suspend() && mesg.event == PM_EVENT_SUSPEND) { if (piix_broken_suspend() && (mesg.event & PM_EVENT_SLEEP)) {
pci_save_state(pdev); pci_save_state(pdev);
/* mark its power state as "unknown", since we don't /* mark its power state as "unknown", since we don't

View file

@ -7368,7 +7368,7 @@ void ata_pci_device_do_suspend(struct pci_dev *pdev, pm_message_t mesg)
pci_save_state(pdev); pci_save_state(pdev);
pci_disable_device(pdev); pci_disable_device(pdev);
if (mesg.event == PM_EVENT_SUSPEND) if (mesg.event & PM_EVENT_SLEEP)
pci_set_power_state(pdev, PCI_D3hot); pci_set_power_state(pdev, PCI_D3hot);
} }

View file

@ -1254,7 +1254,7 @@ pmac_ide_macio_suspend(struct macio_dev *mdev, pm_message_t mesg)
int rc = 0; int rc = 0;
if (mesg.event != mdev->ofdev.dev.power.power_state.event if (mesg.event != mdev->ofdev.dev.power.power_state.event
&& mesg.event == PM_EVENT_SUSPEND) { && (mesg.event & PM_EVENT_SLEEP)) {
rc = pmac_ide_do_suspend(hwif); rc = pmac_ide_do_suspend(hwif);
if (rc == 0) if (rc == 0)
mdev->ofdev.dev.power.power_state = mesg; mdev->ofdev.dev.power.power_state = mesg;
@ -1364,7 +1364,7 @@ pmac_ide_pci_suspend(struct pci_dev *pdev, pm_message_t mesg)
int rc = 0; int rc = 0;
if (mesg.event != pdev->dev.power.power_state.event if (mesg.event != pdev->dev.power.power_state.event
&& mesg.event == PM_EVENT_SUSPEND) { && (mesg.event & PM_EVENT_SLEEP)) {
rc = pmac_ide_do_suspend(hwif); rc = pmac_ide_do_suspend(hwif);
if (rc == 0) if (rc == 0)
pdev->dev.power.power_state = mesg; pdev->dev.power.power_state = mesg;

View file

@ -698,7 +698,8 @@ static int media_bay_suspend(struct macio_dev *mdev, pm_message_t state)
{ {
struct media_bay_info *bay = macio_get_drvdata(mdev); struct media_bay_info *bay = macio_get_drvdata(mdev);
if (state.event != mdev->ofdev.dev.power.power_state.event && state.event == PM_EVENT_SUSPEND) { if (state.event != mdev->ofdev.dev.power.power_state.event
&& (state.event & PM_EVENT_SLEEP)) {
down(&bay->lock); down(&bay->lock);
bay->sleeping = 1; bay->sleeping = 1;
set_mb_power(bay, 0); set_mb_power(bay, 0);

View file

@ -554,6 +554,7 @@ pci_power_t pci_choose_state(struct pci_dev *dev, pm_message_t state)
case PM_EVENT_PRETHAW: case PM_EVENT_PRETHAW:
/* REVISIT both freeze and pre-thaw "should" use D0 */ /* REVISIT both freeze and pre-thaw "should" use D0 */
case PM_EVENT_SUSPEND: case PM_EVENT_SUSPEND:
case PM_EVENT_HIBERNATE:
return PCI_D3hot; return PCI_D3hot;
default: default:
printk("Unrecognized suspend event %d\n", state.event); printk("Unrecognized suspend event %d\n", state.event);

View file

@ -89,7 +89,7 @@ ahd_linux_pci_dev_suspend(struct pci_dev *pdev, pm_message_t mesg)
pci_save_state(pdev); pci_save_state(pdev);
pci_disable_device(pdev); pci_disable_device(pdev);
if (mesg.event == PM_EVENT_SUSPEND) if (mesg.event & PM_EVENT_SLEEP)
pci_set_power_state(pdev, PCI_D3hot); pci_set_power_state(pdev, PCI_D3hot);
return rc; return rc;

View file

@ -134,7 +134,7 @@ ahc_linux_pci_dev_suspend(struct pci_dev *pdev, pm_message_t mesg)
pci_save_state(pdev); pci_save_state(pdev);
pci_disable_device(pdev); pci_disable_device(pdev);
if (mesg.event == PM_EVENT_SUSPEND) if (mesg.event & PM_EVENT_SLEEP)
pci_set_power_state(pdev, PCI_D3hot); pci_set_power_state(pdev, PCI_D3hot);
return rc; return rc;

View file

@ -1759,6 +1759,7 @@ static int mesh_suspend(struct macio_dev *mdev, pm_message_t mesg)
switch (mesg.event) { switch (mesg.event) {
case PM_EVENT_SUSPEND: case PM_EVENT_SUSPEND:
case PM_EVENT_HIBERNATE:
case PM_EVENT_FREEZE: case PM_EVENT_FREEZE:
break; break;
default: default:

View file

@ -1835,8 +1835,7 @@ static int sd_suspend(struct device *dev, pm_message_t mesg)
goto done; goto done;
} }
if (mesg.event == PM_EVENT_SUSPEND && if ((mesg.event & PM_EVENT_SLEEP) && sdkp->device->manage_start_stop) {
sdkp->device->manage_start_stop) {
sd_printk(KERN_NOTICE, sdkp, "Stopping disk\n"); sd_printk(KERN_NOTICE, sdkp, "Stopping disk\n");
ret = sd_start_stop_device(sdkp, 0); ret = sd_start_stop_device(sdkp, 0);
} }

View file

@ -1766,6 +1766,7 @@ sl811h_suspend(struct platform_device *dev, pm_message_t state)
retval = sl811h_bus_suspend(hcd); retval = sl811h_bus_suspend(hcd);
break; break;
case PM_EVENT_SUSPEND: case PM_EVENT_SUSPEND:
case PM_EVENT_HIBERNATE:
case PM_EVENT_PRETHAW: /* explicitly discard hw state */ case PM_EVENT_PRETHAW: /* explicitly discard hw state */
port_power(sl811, 0); port_power(sl811, 0);
break; break;

View file

@ -3214,14 +3214,19 @@ static int u132_suspend(struct platform_device *pdev, pm_message_t state)
return -ESHUTDOWN; return -ESHUTDOWN;
} else { } else {
int retval = 0; int retval = 0;
if (state.event == PM_EVENT_FREEZE) {
switch (state.event) {
case PM_EVENT_FREEZE:
retval = u132_bus_suspend(hcd); retval = u132_bus_suspend(hcd);
} else if (state.event == PM_EVENT_SUSPEND) { break;
case PM_EVENT_SUSPEND:
case PM_EVENT_HIBERNATE:
int ports = MAX_U132_PORTS; int ports = MAX_U132_PORTS;
while (ports-- > 0) { while (ports-- > 0) {
port_power(u132, ports, 0); port_power(u132, ports, 0);
} }
} break;
}
if (retval == 0) if (retval == 0)
pdev->dev.power.power_state = state; pdev->dev.power.power_state = state;
return retval; return retval;

View file

@ -459,7 +459,7 @@ static int chipsfb_pci_suspend(struct pci_dev *pdev, pm_message_t state)
if (state.event == pdev->dev.power.power_state.event) if (state.event == pdev->dev.power.power_state.event)
return 0; return 0;
if (state.event != PM_EVENT_SUSPEND) if (!(state.event & PM_EVENT_SLEEP))
goto done; goto done;
acquire_console_sem(); acquire_console_sem();

View file

@ -1066,7 +1066,7 @@ static int nvidiafb_suspend(struct pci_dev *dev, pm_message_t mesg)
acquire_console_sem(); acquire_console_sem();
par->pm_state = mesg.event; par->pm_state = mesg.event;
if (mesg.event == PM_EVENT_SUSPEND) { if (mesg.event & PM_EVENT_SLEEP) {
fb_set_suspend(info, 1); fb_set_suspend(info, 1);
nvidiafb_blank(FB_BLANK_POWERDOWN, info); nvidiafb_blank(FB_BLANK_POWERDOWN, info);
nvidia_write_regs(par, &par->SavedReg); nvidia_write_regs(par, &par->SavedReg);

View file

@ -143,6 +143,9 @@ typedef struct pm_message {
* the upcoming system state (such as PCI_D3hot), and enable * the upcoming system state (such as PCI_D3hot), and enable
* wakeup events as appropriate. * wakeup events as appropriate.
* *
* HIBERNATE Enter a low power device state appropriate for the hibernation
* state (eg. ACPI S4) and enable wakeup events as appropriate.
*
* FREEZE Quiesce operations so that a consistent image can be saved; * FREEZE Quiesce operations so that a consistent image can be saved;
* but do NOT otherwise enter a low power device state, and do * but do NOT otherwise enter a low power device state, and do
* NOT emit system wakeup events. * NOT emit system wakeup events.
@ -166,11 +169,15 @@ typedef struct pm_message {
#define PM_EVENT_ON 0 #define PM_EVENT_ON 0
#define PM_EVENT_FREEZE 1 #define PM_EVENT_FREEZE 1
#define PM_EVENT_SUSPEND 2 #define PM_EVENT_SUSPEND 2
#define PM_EVENT_PRETHAW 3 #define PM_EVENT_HIBERNATE 4
#define PM_EVENT_PRETHAW 8
#define PM_EVENT_SLEEP (PM_EVENT_SUSPEND | PM_EVENT_HIBERNATE)
#define PMSG_FREEZE ((struct pm_message){ .event = PM_EVENT_FREEZE, }) #define PMSG_FREEZE ((struct pm_message){ .event = PM_EVENT_FREEZE, })
#define PMSG_PRETHAW ((struct pm_message){ .event = PM_EVENT_PRETHAW, }) #define PMSG_PRETHAW ((struct pm_message){ .event = PM_EVENT_PRETHAW, })
#define PMSG_SUSPEND ((struct pm_message){ .event = PM_EVENT_SUSPEND, }) #define PMSG_SUSPEND ((struct pm_message){ .event = PM_EVENT_SUSPEND, })
#define PMSG_HIBERNATE ((struct pm_message){ .event = PM_EVENT_HIBERNATE, })
#define PMSG_ON ((struct pm_message){ .event = PM_EVENT_ON, }) #define PMSG_ON ((struct pm_message){ .event = PM_EVENT_ON, })
struct dev_pm_info { struct dev_pm_info {

View file

@ -391,7 +391,7 @@ int hibernation_platform_enter(void)
goto Close; goto Close;
suspend_console(); suspend_console();
error = device_suspend(PMSG_SUSPEND); error = device_suspend(PMSG_HIBERNATE);
if (error) if (error)
goto Resume_console; goto Resume_console;
@ -404,7 +404,7 @@ int hibernation_platform_enter(void)
goto Finish; goto Finish;
local_irq_disable(); local_irq_disable();
error = device_power_down(PMSG_SUSPEND); error = device_power_down(PMSG_HIBERNATE);
if (!error) { if (!error) {
hibernation_ops->enter(); hibernation_ops->enter();
/* We should never get here */ /* We should never get here */

View file

@ -232,7 +232,7 @@ static int rfkill_suspend(struct device *dev, pm_message_t state)
struct rfkill *rfkill = to_rfkill(dev); struct rfkill *rfkill = to_rfkill(dev);
if (dev->power.power_state.event != state.event) { if (dev->power.power_state.event != state.event) {
if (state.event == PM_EVENT_SUSPEND) { if (state.event & PM_EVENT_SLEEP) {
mutex_lock(&rfkill->mutex); mutex_lock(&rfkill->mutex);
if (rfkill->state == RFKILL_STATE_ON) if (rfkill->state == RFKILL_STATE_ON)