Commit 54050a4e authored by Patrick Mochel's avatar Patrick Mochel

[power] Update device handling.

- From conversations with Ben Herrenschmidt. 

Most devices should be able to handle powering down with interrupts enabled,
which I already assume. But since suspending will stop I/O transactions
before the call to power it off (making the device unusable anyway), there
is no need to separate the calls - we may as well make it simpler for 
driver authors and require that driver authors do everything at the same 
time.

There will always be devices that need to either power down or power up the
device with interrupts disabled. They will get called with interrupts 
enabled, but may return -EAGAIN to be called again with interrupts disabled
to do what they need to do.

System devices are now always called only with interrupts disabled. Come 
on - they're system devices. Of course we need interrupts disabled.
parent 72a4a179
...@@ -25,7 +25,6 @@ ...@@ -25,7 +25,6 @@
#include "power.h" #include "power.h"
LIST_HEAD(dpm_active); LIST_HEAD(dpm_active);
LIST_HEAD(dpm_suspended);
LIST_HEAD(dpm_off); LIST_HEAD(dpm_off);
LIST_HEAD(dpm_off_irq); LIST_HEAD(dpm_off_irq);
......
...@@ -31,7 +31,6 @@ extern struct semaphore dpm_sem; ...@@ -31,7 +31,6 @@ extern struct semaphore dpm_sem;
* The PM lists. * The PM lists.
*/ */
extern struct list_head dpm_active; extern struct list_head dpm_active;
extern struct list_head dpm_suspended;
extern struct list_head dpm_off; extern struct list_head dpm_off;
extern struct list_head dpm_off_irq; extern struct list_head dpm_off_irq;
...@@ -61,15 +60,12 @@ extern void dpm_sysfs_remove(struct device *); ...@@ -61,15 +60,12 @@ extern void dpm_sysfs_remove(struct device *);
*/ */
extern int dpm_resume(void); extern int dpm_resume(void);
extern void dpm_power_up(void); extern void dpm_power_up(void);
extern void dpm_power_up_irq(void);
extern void power_up_device(struct device *);
extern int resume_device(struct device *); extern int resume_device(struct device *);
/* /*
* suspend.c * suspend.c
*/ */
extern int suspend_device(struct device *, u32); extern int suspend_device(struct device *, u32);
extern int power_down_device(struct device *, u32);
/* /*
......
...@@ -12,7 +12,6 @@ ...@@ -12,7 +12,6 @@
#include "power.h" #include "power.h"
extern int sysdev_resume(void); extern int sysdev_resume(void);
extern int sysdev_restore(void);
/** /**
...@@ -30,37 +29,22 @@ int resume_device(struct device * dev) ...@@ -30,37 +29,22 @@ int resume_device(struct device * dev)
return 0; return 0;
} }
/** /**
* dpm_resume - Restore all device state. * device_pm_resume - Restore state of each device in system.
* *
* Walk the dpm_suspended list and restore each device. As they are * Restore normal device state and release the dpm_sem.
* resumed, move the devices to the dpm_active list.
*/ */
int dpm_resume(void) void device_pm_resume(void)
{ {
while(!list_empty(&dpm_suspended)) { while(!list_empty(&dpm_off)) {
struct list_head * entry = dpm_suspended.next; struct list_head * entry = dpm_off.next;
struct device * dev = to_device(entry); struct device * dev = to_device(entry);
list_del_init(entry); list_del_init(entry);
resume_device(dev); resume_device(dev);
list_add_tail(entry,&dpm_active); list_add_tail(entry,&dpm_active);
} }
return 0;
}
/**
* device_pm_resume - Restore state of each device in system.
*
* Restore system device state, then common device state. Finally,
* release dpm_sem, as we're done with device PM.
*/
void device_pm_resume(void)
{
sysdev_restore();
dpm_resume();
up(&dpm_sem); up(&dpm_sem);
} }
...@@ -89,65 +73,27 @@ void power_up_device(struct device * dev) ...@@ -89,65 +73,27 @@ void power_up_device(struct device * dev)
* Interrupts must be disabled when calling this. * Interrupts must be disabled when calling this.
*/ */
void dpm_power_up_irq(void) void dpm_power_up(void)
{ {
while(!list_empty(&dpm_off_irq)) { while(!list_empty(&dpm_off_irq)) {
struct list_head * entry = dpm_off_irq.next; struct list_head * entry = dpm_off_irq.next;
list_del_init(entry); list_del_init(entry);
power_up_device(to_device(entry)); power_up_device(to_device(entry));
list_add_tail(entry,&dpm_suspended); list_add_tail(entry,&dpm_active);
}
}
/**
* dpm_power_up - Power on most devices.
*
* Walk the dpm_off list and power each device up. This is used
* to power on devices that were able to power down with interrupts
* enabled.
*/
void dpm_power_up(void)
{
while (!list_empty(&dpm_off)) {
struct list_head * entry = dpm_off.next;
list_del_init(entry);
power_up_device(to_device(entry));
list_add_tail(entry,&dpm_suspended);
} }
} }
/** /**
* device_pm_power_up - Turn on all devices. * device_pm_power_up - Turn on all devices that need special attention.
* *
* First, power on system devices, which must happen with interrupts * Power on system devices then devices that required we shut them down
* disbled. Then, power on devices that also require interrupts disabled. * with interrupts disabled.
* Turn interrupts back on, and finally power up the rest of the normal * Called with interrupts disabled.
* devices.
*/ */
void device_pm_power_up(void) void device_pm_power_up(void)
{ {
sysdev_resume(); sysdev_resume();
dpm_power_up_irq();
local_irq_enable();
dpm_power_up(); dpm_power_up();
} }
/**
* device_resume - resume all the devices in the system
* @level: stage of resume process we're at
*
* This function is deprecated, and should be replaced with appropriate
* calls to device_pm_power_up() and device_pm_resume() above.
*/
void device_resume(u32 level)
{
printk("%s is deprecated. Called from:\n",__FUNCTION__);
dump_stack();
}
...@@ -14,8 +14,6 @@ static void runtime_resume(struct device * dev) ...@@ -14,8 +14,6 @@ static void runtime_resume(struct device * dev)
{ {
if (!dev->power.power_state) if (!dev->power.power_state)
return; return;
power_up_device(dev);
resume_device(dev); resume_device(dev);
} }
...@@ -55,19 +53,11 @@ int dpm_runtime_suspend(struct device * dev, u32 state) ...@@ -55,19 +53,11 @@ int dpm_runtime_suspend(struct device * dev, u32 state)
if (dev->power.power_state) if (dev->power.power_state)
dpm_runtime_resume(dev); dpm_runtime_resume(dev);
error = suspend_device(dev,state); if (!(error = suspend_device(dev,state)))
if (!error) {
error = power_down_device(dev,state);
if (error)
goto ErrResume;
dev->power.power_state = state; dev->power.power_state = state;
}
Done: Done:
up(&dpm_sem); up(&dpm_sem);
return error; return error;
ErrResume:
resume_device(dev);
goto Done;
} }
......
...@@ -11,7 +11,6 @@ ...@@ -11,7 +11,6 @@
#include <linux/device.h> #include <linux/device.h>
#include "power.h" #include "power.h"
extern int sysdev_save(u32 state);
extern int sysdev_suspend(u32 state); extern int sysdev_suspend(u32 state);
/* /*
...@@ -46,7 +45,10 @@ int suspend_device(struct device * dev, u32 state) ...@@ -46,7 +45,10 @@ int suspend_device(struct device * dev, u32 state)
if (!error) { if (!error) {
list_del(&dev->power.entry); list_del(&dev->power.entry);
list_add(&dev->power.entry,&dpm_suspended); list_add(&dev->power.entry,&dpm_off);
} else if (error == -EAGAIN) {
list_del(&dev->power.entry);
list_add(&dev->power.entry,&dpm_off_irq);
} }
return error; return error;
} }
...@@ -57,10 +59,13 @@ int suspend_device(struct device * dev, u32 state) ...@@ -57,10 +59,13 @@ int suspend_device(struct device * dev, u32 state)
* @state: Power state to put each device in. * @state: Power state to put each device in.
* *
* Walk the dpm_active list, call ->suspend() for each device, and move * Walk the dpm_active list, call ->suspend() for each device, and move
* it to dpm_suspended. If we hit a failure with any of the devices, call * it to dpm_off.
* dpm_resume() above to bring the suspended devices back to life. * Check the return value for each. If it returns 0, then we move the
* the device to the dpm_off list. If it returns -EAGAIN, we move it to
* the dpm_off_irq list. If we get a different error, try and back out.
* *
* Have system devices save state last. * If we hit a failure with any of the devices, call device_pm_resume()
* above to bring the suspended devices back to life.
* *
* Note this function leaves dpm_sem held to * Note this function leaves dpm_sem held to
* a) block other devices from registering. * a) block other devices from registering.
...@@ -83,153 +88,56 @@ int device_pm_suspend(u32 state) ...@@ -83,153 +88,56 @@ int device_pm_suspend(u32 state)
if ((error = suspend_device(dev,state))) if ((error = suspend_device(dev,state)))
goto Error; goto Error;
} }
if ((error = sysdev_save(state)))
goto Error;
Done: Done:
return error; return error;
Error: Error:
dpm_resume(); device_pm_resume();
up(&dpm_sem);
goto Done; goto Done;
} }
/** /**
* power_down_device - Put one device in low power state. * dpm_power_down - Power down devices without interrupts.
* @dev: Device.
* @state: Power state to enter.
*/
int power_down_device(struct device * dev, u32 state)
{
struct device_driver * drv = dev->driver;
int error = 0;
if (drv && drv->suspend)
error = drv->suspend(dev,state,SUSPEND_POWER_DOWN);
if (!error) {
list_del(&dev->power.entry);
list_add(&dev->power.entry,&dpm_off);
}
return error;
}
/**
* dpm_power_down - Put all devices in low power state.
* @state: Power state to enter.
*
* Walk the dpm_suspended list (with interrupts enabled) and try
* to power down each each. If any fail with -EAGAIN, they require
* the call to be done with interrupts disabled. So, we move them to
* the dpm_off_irq list.
*
* If the call succeeds, we move each device to the dpm_off list.
*/
static int dpm_power_down(u32 state)
{
while(!list_empty(&dpm_suspended)) {
struct list_head * entry = dpm_suspended.prev;
int error;
error = power_down_device(to_device(entry),state);
if (error) {
if (error == -EAGAIN) {
list_del(entry);
list_add(entry,&dpm_off_irq);
continue;
}
return error;
}
}
return 0;
}
/**
* dpm_power_down_irq - Power down devices without interrupts.
* @state: State to enter. * @state: State to enter.
* *
* Walk the dpm_off_irq list (built by dpm_power_down) and power * Walk the dpm_off_irq list (built by device_pm_suspend) and power
* down each device that requires the call to be made with interrupts * down each device that requires the call to be made with interrupts
* disabled. * disabled.
*/ */
static int dpm_power_down_irq(u32 state) static int dpm_power_down(u32 state)
{ {
struct device * dev;
int error = 0; int error = 0;
list_for_each_entry_reverse(dev,&dpm_off_irq,power.entry) {
if ((error = power_down_device(dev,state)))
break;
}
return error; return error;
} }
/** /**
* device_pm_power_down - Put all devices in low power state. * device_pm_power_down - Shut down special devices.
* @state: Power state to enter. * @state: Power state to enter.
* *
* Walk the dpm_suspended list, calling ->power_down() for each device. * Walk the dpm_off_irq list, calling ->power_down() for each device that
* Check the return value for each. If it returns 0, then we move the * couldn't power down the device with interrupts enabled. When we're
* the device to the dpm_off list. If it returns -EAGAIN, we move it to * done, power down system devices.
* the dpm_off_irq list. If we get a different error, try and back out.
*
* dpm_irq_off is for devices that require interrupts to be disabled to
* either to power down the device or power it back on.
*
* When we're done, we disable interrrupts (!!) and walk the dpm_off_irq
* list to shut down the devices that need interrupts disabled.
*
* This function leaves interrupts disabled on exit, since powering down
* devices should be the very last thing before the system is put into a
* low-power state.
*
* device_pm_power_on() should be called to re-enable interrupts and power
* the devices back on.
*/ */
int device_pm_power_down(u32 state) int device_pm_power_down(u32 state)
{ {
int error = 0; int error = 0;
struct device * dev;
if ((error = dpm_power_down(state))) list_for_each_entry_reverse(dev,&dpm_off_irq,power.entry) {
goto ErrorIRQOn; if ((error = suspend_device(dev,state)))
local_irq_disable(); break;
if ((error = dpm_power_down_irq(state))) }
goto ErrorIRQOff; if (error)
goto Error;
sysdev_suspend(state); if ((error = sysdev_suspend(state)))
goto Error;
Done: Done:
return error; return error;
Error:
ErrorIRQOff:
dpm_power_up_irq();
local_irq_enable();
ErrorIRQOn:
dpm_power_up(); dpm_power_up();
goto Done; goto Done;
} }
/**
* device_suspend - suspend all devices on the device ree
* @state: state we're entering
* @level: Stage of suspend sequence we're in.
*
*
* This function is deprecated. Calls should be replaced with
* appropriate calls to device_pm_suspend() and device_pm_power_down().
*/
int device_suspend(u32 state, u32 level)
{
printk("%s Called from:\n",__FUNCTION__);
dump_stack();
return -EFAULT;
}
...@@ -282,62 +282,17 @@ void sysdev_shutdown(void) ...@@ -282,62 +282,17 @@ void sysdev_shutdown(void)
} }
/**
* sysdev_save - Save system device state
* @state: Power state we're entering.
*
* This is called when the system is going to sleep, but before interrupts
* have been disabled. This allows system device drivers to allocate and
* save device state, including sleeping during the process..
*/
int sysdev_save(u32 state)
{
struct sysdev_class * cls;
pr_debug("Saving System Device State\n");
down_write(&system_subsys.rwsem);
list_for_each_entry_reverse(cls,&system_subsys.kset.list,
kset.kobj.entry) {
struct sys_device * sysdev;
pr_debug("Saving state for type '%s':\n",cls->kset.kobj.name);
list_for_each_entry(sysdev,&cls->kset.list,kobj.entry) {
struct sysdev_driver * drv;
pr_debug(" %s\n",sysdev->kobj.name);
list_for_each_entry(drv,&global_drivers,entry) {
if (drv->save)
drv->save(sysdev,state);
}
list_for_each_entry(drv,&cls->drivers,entry) {
if (drv->save)
drv->save(sysdev,state);
}
if (cls->save)
cls->save(sysdev,state);
}
}
up_write(&system_subsys.rwsem);
return 0;
}
/** /**
* sysdev_suspend - Suspend all system devices. * sysdev_suspend - Suspend all system devices.
* @state: Power state to enter. * @state: Power state to enter.
* *
* We perform an almost identical operation as sys_device_shutdown() * We perform an almost identical operation as sys_device_shutdown()
* above, though calling ->suspend() instead. * above, though calling ->suspend() instead. Interrupts are disabled
* when this called. Devices are responsible for both saving state and
* quiescing or powering down the device.
* *
* Note: Interrupts are disabled when called, so we can't sleep when * This is only called by the device PM core, so we let them handle
* trying to get the subsystem's rwsem. If that happens, print a nasty * all synchronization.
* warning and return an error.
*/ */
int sysdev_suspend(u32 state) int sysdev_suspend(u32 state)
...@@ -346,11 +301,6 @@ int sysdev_suspend(u32 state) ...@@ -346,11 +301,6 @@ int sysdev_suspend(u32 state)
pr_debug("Suspending System Devices\n"); pr_debug("Suspending System Devices\n");
if (!down_write_trylock(&system_subsys.rwsem)) {
printk("%s: Cannot acquire semaphore; Failing\n",__FUNCTION__);
return -EFAULT;
}
list_for_each_entry_reverse(cls,&system_subsys.kset.list, list_for_each_entry_reverse(cls,&system_subsys.kset.list,
kset.kobj.entry) { kset.kobj.entry) {
struct sys_device * sysdev; struct sys_device * sysdev;
...@@ -378,8 +328,6 @@ int sysdev_suspend(u32 state) ...@@ -378,8 +328,6 @@ int sysdev_suspend(u32 state)
cls->suspend(sysdev,state); cls->suspend(sysdev,state);
} }
} }
up_write(&system_subsys.rwsem);
return 0; return 0;
} }
...@@ -390,7 +338,7 @@ int sysdev_suspend(u32 state) ...@@ -390,7 +338,7 @@ int sysdev_suspend(u32 state)
* Similar to sys_device_suspend(), but we iterate the list forwards * Similar to sys_device_suspend(), but we iterate the list forwards
* to guarantee that parent devices are resumed before their children. * to guarantee that parent devices are resumed before their children.
* *
* Note: Interrupts are disabled when called. * Note: Interrupts are disabled when called.
*/ */
int sysdev_resume(void) int sysdev_resume(void)
...@@ -399,9 +347,6 @@ int sysdev_resume(void) ...@@ -399,9 +347,6 @@ int sysdev_resume(void)
pr_debug("Resuming System Devices\n"); pr_debug("Resuming System Devices\n");
if(!down_write_trylock(&system_subsys.rwsem))
return -EFAULT;
list_for_each_entry(cls,&system_subsys.kset.list,kset.kobj.entry) { list_for_each_entry(cls,&system_subsys.kset.list,kset.kobj.entry) {
struct sys_device * sysdev; struct sys_device * sysdev;
...@@ -429,50 +374,6 @@ int sysdev_resume(void) ...@@ -429,50 +374,6 @@ int sysdev_resume(void)
} }
} }
up_write(&system_subsys.rwsem);
return 0;
}
/**
* sysdev_restore - Restore system device state
*
* This is called during a suspend/resume cycle last, after interrupts
* have been re-enabled. This is intended for auxillary drivers, etc,
* that may sleep when restoring state.
*/
int sysdev_restore(void)
{
struct sysdev_class * cls;
down_write(&system_subsys.rwsem);
pr_debug("Restoring System Device State\n");
list_for_each_entry(cls,&system_subsys.kset.list,kset.kobj.entry) {
struct sys_device * sysdev;
pr_debug("Restoring state for type '%s':\n",cls->kset.kobj.name);
list_for_each_entry(sysdev,&cls->kset.list,kobj.entry) {
struct sysdev_driver * drv;
pr_debug(" %s\n",sysdev->kobj.name);
if (cls->restore)
cls->restore(sysdev);
list_for_each_entry(drv,&cls->drivers,entry) {
if (drv->restore)
drv->restore(sysdev);
}
list_for_each_entry(drv,&global_drivers,entry) {
if (drv->restore)
drv->restore(sysdev);
}
}
}
up_write(&system_subsys.rwsem);
return 0; return 0;
} }
......
...@@ -372,8 +372,6 @@ extern struct bus_type platform_bus_type; ...@@ -372,8 +372,6 @@ extern struct bus_type platform_bus_type;
extern struct device legacy_bus; extern struct device legacy_bus;
/* drivers/base/power.c */ /* drivers/base/power.c */
extern int device_suspend(u32 state, u32 level);
extern void device_resume(u32 level);
extern void device_shutdown(void); extern void device_shutdown(void);
......
...@@ -31,10 +31,8 @@ struct sysdev_class { ...@@ -31,10 +31,8 @@ struct sysdev_class {
/* Default operations for these types of devices */ /* Default operations for these types of devices */
int (*shutdown)(struct sys_device *); int (*shutdown)(struct sys_device *);
int (*save)(struct sys_device *, u32 state);
int (*suspend)(struct sys_device *, u32 state); int (*suspend)(struct sys_device *, u32 state);
int (*resume)(struct sys_device *); int (*resume)(struct sys_device *);
int (*restore)(struct sys_device *);
struct kset kset; struct kset kset;
}; };
...@@ -52,10 +50,8 @@ struct sysdev_driver { ...@@ -52,10 +50,8 @@ struct sysdev_driver {
int (*add)(struct sys_device *); int (*add)(struct sys_device *);
int (*remove)(struct sys_device *); int (*remove)(struct sys_device *);
int (*shutdown)(struct sys_device *); int (*shutdown)(struct sys_device *);
int (*save)(struct sys_device *, u32 state);
int (*suspend)(struct sys_device *, u32 state); int (*suspend)(struct sys_device *, u32 state);
int (*resume)(struct sys_device *); int (*resume)(struct sys_device *);
int (*restore)(struct sys_device *);
}; };
......
...@@ -65,9 +65,9 @@ static int pm_suspend_standby(void) ...@@ -65,9 +65,9 @@ static int pm_suspend_standby(void)
if (!pm_ops || !pm_ops->enter) if (!pm_ops || !pm_ops->enter)
return -EPERM; return -EPERM;
local_irq_save(flags);
if ((error = device_pm_power_down(PM_SUSPEND_STANDBY))) if ((error = device_pm_power_down(PM_SUSPEND_STANDBY)))
goto Done; goto Done;
local_irq_save(flags);
error = pm_ops->enter(PM_SUSPEND_STANDBY); error = pm_ops->enter(PM_SUSPEND_STANDBY);
local_irq_restore(flags); local_irq_restore(flags);
device_pm_power_up(); device_pm_power_up();
...@@ -91,9 +91,9 @@ static int pm_suspend_mem(void) ...@@ -91,9 +91,9 @@ static int pm_suspend_mem(void)
if (!pm_ops || !pm_ops->enter) if (!pm_ops || !pm_ops->enter)
return -EPERM; return -EPERM;
local_irq_save(flags);
if ((error = device_pm_power_down(PM_SUSPEND_STANDBY))) if ((error = device_pm_power_down(PM_SUSPEND_STANDBY)))
goto Done; goto Done;
local_irq_save(flags);
error = pm_ops->enter(PM_SUSPEND_STANDBY); error = pm_ops->enter(PM_SUSPEND_STANDBY);
local_irq_restore(flags); local_irq_restore(flags);
device_pm_power_up(); device_pm_power_up();
...@@ -114,9 +114,19 @@ static int pm_suspend_mem(void) ...@@ -114,9 +114,19 @@ static int pm_suspend_mem(void)
static int power_down(u32 mode) static int power_down(u32 mode)
{ {
unsigned long flags;
int error = 0;
local_irq_save(flags);
device_pm_power_down();
switch(mode) { switch(mode) {
case PM_DISK_PLATFORM: case PM_DISK_PLATFORM:
return pm_ops->enter(PM_SUSPEND_DISK); error = pm_ops->enter(PM_SUSPEND_DISK);
if (error) {
device_pm_power_up();
local_irq_restore(flags);
return error;
}
case PM_DISK_SHUTDOWN: case PM_DISK_SHUTDOWN:
machine_power_off(); machine_power_off();
break; break;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment