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) ...@@ -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 @@ ...@@ -16,6 +16,7 @@
#include <linux/init.h> #include <linux/init.h>
#include <linux/string.h> #include <linux/string.h>
#include "base.h" #include "base.h"
#include "power/power.h"
#define to_dev(node) container_of(node,struct device,bus_list) #define to_dev(node) container_of(node,struct device,bus_list)
#define to_drv(node) container_of(node,struct device_driver,kobj.entry) #define to_drv(node) container_of(node,struct device_driver,kobj.entry)
...@@ -364,6 +365,7 @@ void device_release_driver(struct device * dev) ...@@ -364,6 +365,7 @@ void device_release_driver(struct device * dev)
if (drv) { if (drv) {
sysfs_remove_link(&drv->kobj,dev->kobj.name); sysfs_remove_link(&drv->kobj,dev->kobj.name);
list_del_init(&dev->driver_list); list_del_init(&dev->driver_list);
device_detach_shutdown(dev);
if (drv->remove) if (drv->remove)
drv->remove(dev); drv->remove(dev);
dev->driver = NULL; dev->driver = NULL;
......
...@@ -20,6 +20,7 @@ ...@@ -20,6 +20,7 @@
#include <asm/semaphore.h> #include <asm/semaphore.h>
#include "base.h" #include "base.h"
#include "power/power.h"
int (*platform_notify)(struct device * dev) = NULL; int (*platform_notify)(struct device * dev) = NULL;
int (*platform_notify_remove)(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) ...@@ -90,8 +90,40 @@ device_write_power(struct device * dev, const char * buf, size_t count)
static DEVICE_ATTR(power,S_IWUSR | S_IRUGO, static DEVICE_ATTR(power,S_IWUSR | S_IRUGO,
device_read_power,device_write_power); 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[] = { struct attribute * dev_default_attrs[] = {
&dev_attr_name.attr, &dev_attr_name.attr,
&dev_attr_power.attr, &dev_attr_power.attr,
&dev_attr_detach_state.attr,
NULL, 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. * Used to synchronize global power management operations.
*/ */
...@@ -23,6 +46,8 @@ static inline struct device * to_device(struct list_head * entry) ...@@ -23,6 +46,8 @@ static inline struct device * to_device(struct list_head * entry)
return container_of(to_pm_info(entry),struct device,power); 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 * sysfs.c
...@@ -54,3 +79,26 @@ extern int power_down_device(struct device *, u32); ...@@ -54,3 +79,26 @@ extern int power_down_device(struct device *, u32);
extern int dpm_runtime_suspend(struct device *, u32); extern int dpm_runtime_suspend(struct device *, u32);
extern void dpm_runtime_resume(struct device *); 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 @@ ...@@ -13,10 +13,27 @@
#include <linux/device.h> #include <linux/device.h>
#include <asm/semaphore.h> #include <asm/semaphore.h>
#include "power.h"
#define to_dev(node) container_of(node,struct device,kobj.entry) #define to_dev(node) container_of(node,struct device,kobj.entry)
extern struct subsystem devices_subsys; 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 * We handle system devices differently - we suspend and shut them
* down first and resume them first. That way, we do anything stupid like * down first and resume them first. That way, we do anything stupid like
......
...@@ -270,6 +270,9 @@ struct device { ...@@ -270,6 +270,9 @@ struct device {
being off. */ being off. */
unsigned char *saved_state; /* saved device state */ 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) */ u64 *dma_mask; /* dma mask (if dma'able device) */
void (*release)(struct device * dev); 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