Commit 47149728 authored by Patrick Mochel's avatar Patrick Mochel

[driver model] Allow per-device shutdown or suspend on driver detach.

- Add struct device::detach_state, which tells the core what state to put
  the device in when it's detached from its driver (on module removal).

  This is a value in the range of 0-4, with 0 being On and meaning 'Do 
  Nothing', 4 being Off, meaing calling ->shutdown() for the device, and 
  1-3 being low-power states, meaning call ->suspend() for the device. 

- Add per-device sysfs file 'detach_state' to control the value of the 
  field. 

- Add device_device_shutdown() function, and call it from bus.c::
  device_detach_driver().
parent 9bdbe787
......@@ -15,19 +15,3 @@ struct class_device_attribute *to_class_dev_attr(struct attribute *_attr)
}
#ifdef CONFIG_PM
extern int device_pm_add(struct device *);
extern void device_pm_remove(struct device *);
#else
static inline int device_pm_add(struct device * dev)
{
return 0;
}
static inline void device_pm_remove(struct device * dev)
{
}
#endif
......@@ -16,6 +16,7 @@
#include <linux/init.h>
#include <linux/string.h>
#include "base.h"
#include "power/power.h"
#define to_dev(node) container_of(node,struct device,bus_list)
#define to_drv(node) container_of(node,struct device_driver,kobj.entry)
......@@ -364,6 +365,7 @@ void device_release_driver(struct device * dev)
if (drv) {
sysfs_remove_link(&drv->kobj,dev->kobj.name);
list_del_init(&dev->driver_list);
device_detach_shutdown(dev);
if (drv->remove)
drv->remove(dev);
dev->driver = NULL;
......
......@@ -20,6 +20,7 @@
#include <asm/semaphore.h>
#include "base.h"
#include "power/power.h"
int (*platform_notify)(struct device * dev) = NULL;
int (*platform_notify_remove)(struct device * dev) = NULL;
......
......@@ -90,8 +90,40 @@ device_write_power(struct device * dev, const char * buf, size_t count)
static DEVICE_ATTR(power,S_IWUSR | S_IRUGO,
device_read_power,device_write_power);
/**
* detach_state - control the default power state for the device.
*
* This is the state the device enters when it's driver module is
* unloaded. The value is an unsigned integer, in the range of 0-4.
* '0' indicates 'On', so no action will be taken when the driver is
* unloaded. This is the default behavior.
* '4' indicates 'Off', meaning the driver core will call the driver's
* shutdown method to quiesce the device.
* 1-3 indicate a low-power state for the device to enter via the
* driver's suspend method.
*/
static ssize_t detach_show(struct device * dev, char * buf)
{
return sprintf(buf,"%u\n",dev->detach_state);
}
static ssize_t detach_store(struct device * dev, const char * buf, size_t n)
{
u32 state;
state = simple_strtoul(buf,NULL,10);
if (state > 4)
return -EINVAL;
dev->detach_state = state;
return n;
}
static DEVICE_ATTR(detach_state,0644,detach_show,detach_store);
struct attribute * dev_default_attrs[] = {
&dev_attr_name.attr,
&dev_attr_power.attr,
&dev_attr_detach_state.attr,
NULL,
};
enum {
DEVICE_PM_ON,
DEVICE_PM1,
DEVICE_PM2,
DEVICE_PM3,
DEVICE_PM_OFF,
};
/*
* shutdown.c
*/
extern int device_detach_shutdown(struct device *);
extern void device_shutdown(void);
#ifdef CONFIG_PM
/*
* main.c
*/
/*
* Used to synchronize global power management operations.
*/
......@@ -23,6 +46,8 @@ static inline struct device * to_device(struct list_head * entry)
return container_of(to_pm_info(entry),struct device,power);
}
extern int device_pm_add(struct device *);
extern void device_pm_remove(struct device *);
/*
* sysfs.c
......@@ -54,3 +79,26 @@ extern int power_down_device(struct device *, u32);
extern int dpm_runtime_suspend(struct device *, u32);
extern void dpm_runtime_resume(struct device *);
#else /* CONFIG_PM */
static inline int device_pm_add(struct device * dev)
{
return 0;
}
static inline void device_pm_remove(struct device * dev)
{
}
static inline int dpm_runtime_suspend(struct device * dev, u32 state)
{
return 0;
}
static inline void dpm_runtime_resume(struct device * dev)
{
}
#endif
......@@ -13,10 +13,27 @@
#include <linux/device.h>
#include <asm/semaphore.h>
#include "power.h"
#define to_dev(node) container_of(node,struct device,kobj.entry)
extern struct subsystem devices_subsys;
int device_detach_shutdown(struct device * dev)
{
if (!dev->detach_state)
return 0;
if (dev->detach_state == DEVICE_PM_OFF) {
if (dev->driver && dev->driver->shutdown)
dev->driver->shutdown(dev);
return 0;
}
return dpm_runtime_suspend(dev,dev->detach_state);
}
/**
* We handle system devices differently - we suspend and shut them
* down first and resume them first. That way, we do anything stupid like
......
......@@ -270,6 +270,9 @@ struct device {
being off. */
unsigned char *saved_state; /* saved device state */
u32 detach_state; /* State to enter when device is
detached from its driver. */
u64 *dma_mask; /* dma mask (if dma'able device) */
void (*release)(struct device * dev);
......
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