Creation of Cybook 2416 (actually Gen4) repository
This commit is contained in:
10
drivers/base/power/Makefile
Normal file
10
drivers/base/power/Makefile
Normal file
@@ -0,0 +1,10 @@
|
||||
obj-y := shutdown.o
|
||||
obj-$(CONFIG_PM) += main.o suspend.o resume.o runtime.o sysfs.o
|
||||
obj-$(CONFIG_PM_TRACE) += trace.o
|
||||
|
||||
ifeq ($(CONFIG_DEBUG_DRIVER),y)
|
||||
EXTRA_CFLAGS += -DDEBUG
|
||||
endif
|
||||
ifeq ($(CONFIG_PM_DEBUG),y)
|
||||
EXTRA_CFLAGS += -DDEBUG
|
||||
endif
|
||||
80
drivers/base/power/main.c
Normal file
80
drivers/base/power/main.c
Normal file
@@ -0,0 +1,80 @@
|
||||
/*
|
||||
* drivers/base/power/main.c - Where the driver meets power management.
|
||||
*
|
||||
* Copyright (c) 2003 Patrick Mochel
|
||||
* Copyright (c) 2003 Open Source Development Lab
|
||||
*
|
||||
* This file is released under the GPLv2
|
||||
*
|
||||
*
|
||||
* The driver model core calls device_pm_add() when a device is registered.
|
||||
* This will intialize the embedded device_pm_info object in the device
|
||||
* and add it to the list of power-controlled devices. sysfs entries for
|
||||
* controlling device power management will also be added.
|
||||
*
|
||||
* A different set of lists than the global subsystem list are used to
|
||||
* keep track of power info because we use different lists to hold
|
||||
* devices based on what stage of the power management process they
|
||||
* are in. The power domain dependencies may also differ from the
|
||||
* ancestral dependencies that the subsystem list maintains.
|
||||
*/
|
||||
|
||||
#include <linux/device.h>
|
||||
#include "power.h"
|
||||
|
||||
LIST_HEAD(dpm_active);
|
||||
LIST_HEAD(dpm_off);
|
||||
LIST_HEAD(dpm_off_irq);
|
||||
|
||||
DECLARE_MUTEX(dpm_sem);
|
||||
DECLARE_MUTEX(dpm_list_sem);
|
||||
|
||||
/**
|
||||
* device_pm_set_parent - Specify power dependency.
|
||||
* @dev: Device who needs power.
|
||||
* @parent: Device that supplies power.
|
||||
*
|
||||
* This function is used to manually describe a power-dependency
|
||||
* relationship. It may be used to specify a transversal relationship
|
||||
* (where the power supplier is not the physical (or electrical)
|
||||
* ancestor of a specific device.
|
||||
* The effect of this is that the supplier will not be powered down
|
||||
* before the power dependent.
|
||||
*/
|
||||
|
||||
void device_pm_set_parent(struct device * dev, struct device * parent)
|
||||
{
|
||||
put_device(dev->power.pm_parent);
|
||||
dev->power.pm_parent = get_device(parent);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(device_pm_set_parent);
|
||||
|
||||
int device_pm_add(struct device * dev)
|
||||
{
|
||||
int error;
|
||||
|
||||
pr_debug("PM: Adding info for %s:%s\n",
|
||||
dev->bus ? dev->bus->name : "No Bus",
|
||||
kobject_name(&dev->kobj));
|
||||
down(&dpm_list_sem);
|
||||
list_add_tail(&dev->power.entry, &dpm_active);
|
||||
device_pm_set_parent(dev, dev->parent);
|
||||
if ((error = dpm_sysfs_add(dev)))
|
||||
list_del(&dev->power.entry);
|
||||
up(&dpm_list_sem);
|
||||
return error;
|
||||
}
|
||||
|
||||
void device_pm_remove(struct device * dev)
|
||||
{
|
||||
pr_debug("PM: Removing info for %s:%s\n",
|
||||
dev->bus ? dev->bus->name : "No Bus",
|
||||
kobject_name(&dev->kobj));
|
||||
down(&dpm_list_sem);
|
||||
dpm_sysfs_remove(dev);
|
||||
put_device(dev->power.pm_parent);
|
||||
list_del_init(&dev->power.entry);
|
||||
up(&dpm_list_sem);
|
||||
}
|
||||
|
||||
|
||||
82
drivers/base/power/power.h
Normal file
82
drivers/base/power/power.h
Normal file
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* shutdown.c
|
||||
*/
|
||||
|
||||
extern void device_shutdown(void);
|
||||
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
|
||||
/*
|
||||
* main.c
|
||||
*/
|
||||
|
||||
/*
|
||||
* Used to synchronize global power management operations.
|
||||
*/
|
||||
extern struct semaphore dpm_sem;
|
||||
|
||||
/*
|
||||
* Used to serialize changes to the dpm_* lists.
|
||||
*/
|
||||
extern struct semaphore dpm_list_sem;
|
||||
|
||||
/*
|
||||
* The PM lists.
|
||||
*/
|
||||
extern struct list_head dpm_active;
|
||||
extern struct list_head dpm_off;
|
||||
extern struct list_head dpm_off_irq;
|
||||
|
||||
|
||||
static inline struct dev_pm_info * to_pm_info(struct list_head * entry)
|
||||
{
|
||||
return container_of(entry, struct dev_pm_info, entry);
|
||||
}
|
||||
|
||||
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
|
||||
*/
|
||||
|
||||
extern int dpm_sysfs_add(struct device *);
|
||||
extern void dpm_sysfs_remove(struct device *);
|
||||
|
||||
/*
|
||||
* resume.c
|
||||
*/
|
||||
|
||||
extern void dpm_resume(void);
|
||||
extern void dpm_power_up(void);
|
||||
extern int resume_device(struct device *);
|
||||
|
||||
/*
|
||||
* suspend.c
|
||||
*/
|
||||
extern int suspend_device(struct device *, pm_message_t);
|
||||
|
||||
|
||||
/*
|
||||
* runtime.c
|
||||
*/
|
||||
|
||||
#else /* CONFIG_PM */
|
||||
|
||||
|
||||
static inline int device_pm_add(struct device * dev)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
static inline void device_pm_remove(struct device * dev)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
147
drivers/base/power/resume.c
Normal file
147
drivers/base/power/resume.c
Normal file
@@ -0,0 +1,147 @@
|
||||
/*
|
||||
* resume.c - Functions for waking devices up.
|
||||
*
|
||||
* Copyright (c) 2003 Patrick Mochel
|
||||
* Copyright (c) 2003 Open Source Development Labs
|
||||
*
|
||||
* This file is released under the GPLv2
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/device.h>
|
||||
#include <linux/resume-trace.h>
|
||||
#include "../base.h"
|
||||
#include "power.h"
|
||||
|
||||
|
||||
/**
|
||||
* resume_device - Restore state for one device.
|
||||
* @dev: Device.
|
||||
*
|
||||
*/
|
||||
|
||||
int resume_device(struct device * dev)
|
||||
{
|
||||
int error = 0;
|
||||
|
||||
TRACE_DEVICE(dev);
|
||||
TRACE_RESUME(0);
|
||||
down(&dev->sem);
|
||||
if (dev->power.pm_parent
|
||||
&& dev->power.pm_parent->power.power_state.event) {
|
||||
dev_err(dev, "PM: resume from %d, parent %s still %d\n",
|
||||
dev->power.power_state.event,
|
||||
dev->power.pm_parent->bus_id,
|
||||
dev->power.pm_parent->power.power_state.event);
|
||||
}
|
||||
if (dev->bus && dev->bus->resume) {
|
||||
dev_dbg(dev,"resuming\n");
|
||||
error = dev->bus->resume(dev);
|
||||
}
|
||||
if (dev->class && dev->class->resume) {
|
||||
dev_dbg(dev,"class resume\n");
|
||||
error = dev->class->resume(dev);
|
||||
}
|
||||
up(&dev->sem);
|
||||
TRACE_RESUME(error);
|
||||
return error;
|
||||
}
|
||||
|
||||
|
||||
static int resume_device_early(struct device * dev)
|
||||
{
|
||||
int error = 0;
|
||||
|
||||
TRACE_DEVICE(dev);
|
||||
TRACE_RESUME(0);
|
||||
if (dev->bus && dev->bus->resume_early) {
|
||||
dev_dbg(dev,"EARLY resume\n");
|
||||
error = dev->bus->resume_early(dev);
|
||||
}
|
||||
TRACE_RESUME(error);
|
||||
return error;
|
||||
}
|
||||
|
||||
/*
|
||||
* Resume the devices that have either not gone through
|
||||
* the late suspend, or that did go through it but also
|
||||
* went through the early resume
|
||||
*/
|
||||
void dpm_resume(void)
|
||||
{
|
||||
down(&dpm_list_sem);
|
||||
while(!list_empty(&dpm_off)) {
|
||||
struct list_head * entry = dpm_off.next;
|
||||
struct device * dev = to_device(entry);
|
||||
|
||||
get_device(dev);
|
||||
list_move_tail(entry, &dpm_active);
|
||||
|
||||
up(&dpm_list_sem);
|
||||
if (!dev->power.prev_state.event)
|
||||
resume_device(dev);
|
||||
down(&dpm_list_sem);
|
||||
put_device(dev);
|
||||
}
|
||||
up(&dpm_list_sem);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* device_resume - Restore state of each device in system.
|
||||
*
|
||||
* Walk the dpm_off list, remove each entry, resume the device,
|
||||
* then add it to the dpm_active list.
|
||||
*/
|
||||
|
||||
void device_resume(void)
|
||||
{
|
||||
might_sleep();
|
||||
down(&dpm_sem);
|
||||
dpm_resume();
|
||||
up(&dpm_sem);
|
||||
}
|
||||
|
||||
EXPORT_SYMBOL_GPL(device_resume);
|
||||
|
||||
|
||||
/**
|
||||
* dpm_power_up - Power on some devices.
|
||||
*
|
||||
* Walk the dpm_off_irq list and power each device up. This
|
||||
* is used for devices that required they be powered down with
|
||||
* interrupts disabled. As devices are powered on, they are moved
|
||||
* to the dpm_active list.
|
||||
*
|
||||
* Interrupts must be disabled when calling this.
|
||||
*/
|
||||
|
||||
void dpm_power_up(void)
|
||||
{
|
||||
while(!list_empty(&dpm_off_irq)) {
|
||||
struct list_head * entry = dpm_off_irq.next;
|
||||
struct device * dev = to_device(entry);
|
||||
|
||||
list_move_tail(entry, &dpm_off);
|
||||
resume_device_early(dev);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* device_power_up - Turn on all devices that need special attention.
|
||||
*
|
||||
* Power on system devices then devices that required we shut them down
|
||||
* with interrupts disabled.
|
||||
* Called with interrupts disabled.
|
||||
*/
|
||||
|
||||
void device_power_up(void)
|
||||
{
|
||||
sysdev_resume();
|
||||
dpm_power_up();
|
||||
}
|
||||
|
||||
EXPORT_SYMBOL_GPL(device_power_up);
|
||||
|
||||
|
||||
85
drivers/base/power/runtime.c
Normal file
85
drivers/base/power/runtime.c
Normal file
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
* drivers/base/power/runtime.c - Handling dynamic device power management.
|
||||
*
|
||||
* Copyright (c) 2003 Patrick Mochel
|
||||
* Copyright (c) 2003 Open Source Development Lab
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/device.h>
|
||||
#include "power.h"
|
||||
|
||||
|
||||
static void runtime_resume(struct device * dev)
|
||||
{
|
||||
dev_dbg(dev, "resuming\n");
|
||||
if (!dev->power.power_state.event)
|
||||
return;
|
||||
if (!resume_device(dev))
|
||||
dev->power.power_state = PMSG_ON;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* dpm_runtime_resume - Power one device back on.
|
||||
* @dev: Device.
|
||||
*
|
||||
* Bring one device back to the on state by first powering it
|
||||
* on, then restoring state. We only operate on devices that aren't
|
||||
* already on.
|
||||
* FIXME: We need to handle devices that are in an unknown state.
|
||||
*/
|
||||
|
||||
void dpm_runtime_resume(struct device * dev)
|
||||
{
|
||||
down(&dpm_sem);
|
||||
runtime_resume(dev);
|
||||
up(&dpm_sem);
|
||||
}
|
||||
EXPORT_SYMBOL(dpm_runtime_resume);
|
||||
|
||||
|
||||
/**
|
||||
* dpm_runtime_suspend - Put one device in low-power state.
|
||||
* @dev: Device.
|
||||
* @state: State to enter.
|
||||
*/
|
||||
|
||||
int dpm_runtime_suspend(struct device * dev, pm_message_t state)
|
||||
{
|
||||
int error = 0;
|
||||
|
||||
down(&dpm_sem);
|
||||
if (dev->power.power_state.event == state.event)
|
||||
goto Done;
|
||||
|
||||
if (dev->power.power_state.event)
|
||||
runtime_resume(dev);
|
||||
|
||||
if (!(error = suspend_device(dev, state)))
|
||||
dev->power.power_state = state;
|
||||
Done:
|
||||
up(&dpm_sem);
|
||||
return error;
|
||||
}
|
||||
EXPORT_SYMBOL(dpm_runtime_suspend);
|
||||
|
||||
|
||||
#if 0
|
||||
/**
|
||||
* dpm_set_power_state - Update power_state field.
|
||||
* @dev: Device.
|
||||
* @state: Power state device is in.
|
||||
*
|
||||
* This is an update mechanism for drivers to notify the core
|
||||
* what power state a device is in. Device probing code may not
|
||||
* always be able to tell, but we need accurate information to
|
||||
* work reliably.
|
||||
*/
|
||||
void dpm_set_power_state(struct device * dev, pm_message_t state)
|
||||
{
|
||||
down(&dpm_sem);
|
||||
dev->power.power_state = state;
|
||||
up(&dpm_sem);
|
||||
}
|
||||
#endif /* 0 */
|
||||
54
drivers/base/power/shutdown.c
Normal file
54
drivers/base/power/shutdown.c
Normal file
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* shutdown.c - power management functions for the device tree.
|
||||
*
|
||||
* Copyright (c) 2002-3 Patrick Mochel
|
||||
* 2002-3 Open Source Development Lab
|
||||
*
|
||||
* This file is released under the GPLv2
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/device.h>
|
||||
#include <asm/semaphore.h>
|
||||
|
||||
#include "../base.h"
|
||||
#include "power.h"
|
||||
|
||||
#define to_dev(node) container_of(node, struct device, kobj.entry)
|
||||
|
||||
extern struct subsystem devices_subsys;
|
||||
|
||||
|
||||
/**
|
||||
* We handle system devices differently - we suspend and shut them
|
||||
* down last and resume them first. That way, we don't do anything stupid like
|
||||
* shutting down the interrupt controller before any devices..
|
||||
*
|
||||
* Note that there are not different stages for power management calls -
|
||||
* they only get one called once when interrupts are disabled.
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* device_shutdown - call ->shutdown() on each device to shutdown.
|
||||
*/
|
||||
void device_shutdown(void)
|
||||
{
|
||||
struct device * dev, *devn;
|
||||
|
||||
down_write(&devices_subsys.rwsem);
|
||||
list_for_each_entry_safe_reverse(dev, devn, &devices_subsys.kset.list,
|
||||
kobj.entry) {
|
||||
if (dev->bus && dev->bus->shutdown) {
|
||||
dev_dbg(dev, "shutdown\n");
|
||||
dev->bus->shutdown(dev);
|
||||
} else if (dev->driver && dev->driver->shutdown) {
|
||||
dev_dbg(dev, "shutdown\n");
|
||||
dev->driver->shutdown(dev);
|
||||
}
|
||||
}
|
||||
up_write(&devices_subsys.rwsem);
|
||||
|
||||
sysdev_shutdown();
|
||||
}
|
||||
|
||||
237
drivers/base/power/suspend.c
Normal file
237
drivers/base/power/suspend.c
Normal file
@@ -0,0 +1,237 @@
|
||||
/*
|
||||
* suspend.c - Functions for putting devices to sleep.
|
||||
*
|
||||
* Copyright (c) 2003 Patrick Mochel
|
||||
* Copyright (c) 2003 Open Source Development Labs
|
||||
*
|
||||
* This file is released under the GPLv2
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/device.h>
|
||||
#include <linux/kallsyms.h>
|
||||
#include <linux/pm.h>
|
||||
#include "../base.h"
|
||||
#include "power.h"
|
||||
|
||||
#ifdef CONFIG_PM_CPU_MODE
|
||||
extern unsigned char pm_cpu_mode;
|
||||
#endif
|
||||
|
||||
/*
|
||||
* The entries in the dpm_active list are in a depth first order, simply
|
||||
* because children are guaranteed to be discovered after parents, and
|
||||
* are inserted at the back of the list on discovery.
|
||||
*
|
||||
* All list on the suspend path are done in reverse order, so we operate
|
||||
* on the leaves of the device tree (or forests, depending on how you want
|
||||
* to look at it ;) first. As nodes are removed from the back of the list,
|
||||
* they are inserted into the front of their destintation lists.
|
||||
*
|
||||
* Things are the reverse on the resume path - iterations are done in
|
||||
* forward order, and nodes are inserted at the back of their destination
|
||||
* lists. This way, the ancestors will be accessed before their descendents.
|
||||
*/
|
||||
|
||||
static inline char *suspend_verb(u32 event)
|
||||
{
|
||||
switch (event) {
|
||||
case PM_EVENT_SUSPEND: return "suspend";
|
||||
case PM_EVENT_FREEZE: return "freeze";
|
||||
case PM_EVENT_PRETHAW: return "prethaw";
|
||||
default: return "(unknown suspend event)";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* suspend_device - Save state of one device.
|
||||
* @dev: Device.
|
||||
* @state: Power state device is entering.
|
||||
*/
|
||||
|
||||
int suspend_device(struct device * dev, pm_message_t state)
|
||||
{
|
||||
int error = 0;
|
||||
|
||||
down(&dev->sem);
|
||||
if (dev->power.power_state.event) {
|
||||
dev_dbg(dev, "PM: suspend %d-->%d\n",
|
||||
dev->power.power_state.event, state.event);
|
||||
}
|
||||
if (dev->power.pm_parent
|
||||
&& dev->power.pm_parent->power.power_state.event) {
|
||||
dev_err(dev,
|
||||
"PM: suspend %d->%d, parent %s already %d\n",
|
||||
dev->power.power_state.event, state.event,
|
||||
dev->power.pm_parent->bus_id,
|
||||
dev->power.pm_parent->power.power_state.event);
|
||||
}
|
||||
|
||||
dev->power.prev_state = dev->power.power_state;
|
||||
|
||||
if (dev->class && dev->class->suspend && !dev->power.power_state.event) {
|
||||
dev_dbg(dev, "class %s%s\n",
|
||||
suspend_verb(state.event),
|
||||
((state.event == PM_EVENT_SUSPEND)
|
||||
&& device_may_wakeup(dev))
|
||||
? ", may wakeup"
|
||||
: ""
|
||||
);
|
||||
error = dev->class->suspend(dev, state);
|
||||
suspend_report_result(dev->class->suspend, error);
|
||||
}
|
||||
|
||||
if (!error && dev->bus && dev->bus->suspend && !dev->power.power_state.event) {
|
||||
dev_dbg(dev, "%s%s\n",
|
||||
suspend_verb(state.event),
|
||||
((state.event == PM_EVENT_SUSPEND)
|
||||
&& device_may_wakeup(dev))
|
||||
? ", may wakeup"
|
||||
: ""
|
||||
);
|
||||
error = dev->bus->suspend(dev, state);
|
||||
suspend_report_result(dev->bus->suspend, error);
|
||||
}
|
||||
up(&dev->sem);
|
||||
return error;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* This is called with interrupts off, only a single CPU
|
||||
* running. We can't do down() on a semaphore (and we don't
|
||||
* need the protection)
|
||||
*/
|
||||
static int suspend_device_late(struct device *dev, pm_message_t state)
|
||||
{
|
||||
int error = 0;
|
||||
|
||||
if (dev->bus && dev->bus->suspend_late && !dev->power.power_state.event) {
|
||||
dev_dbg(dev, "LATE %s%s\n",
|
||||
suspend_verb(state.event),
|
||||
((state.event == PM_EVENT_SUSPEND)
|
||||
&& device_may_wakeup(dev))
|
||||
? ", may wakeup"
|
||||
: ""
|
||||
);
|
||||
error = dev->bus->suspend_late(dev, state);
|
||||
suspend_report_result(dev->bus->suspend_late, error);
|
||||
}
|
||||
return error;
|
||||
}
|
||||
|
||||
/**
|
||||
* device_suspend - Save state and stop all devices in system.
|
||||
* @state: Power state to put each device in.
|
||||
*
|
||||
* Walk the dpm_active list, call ->suspend() for each device, and move
|
||||
* it to the dpm_off list.
|
||||
*
|
||||
* (For historical reasons, if it returns -EAGAIN, that used to mean
|
||||
* that the device would be called again with interrupts disabled.
|
||||
* These days, we use the "suspend_late()" callback for that, so we
|
||||
* print a warning and consider it an error).
|
||||
*
|
||||
* If we get a different error, try and back out.
|
||||
*
|
||||
* If we hit a failure with any of the devices, call device_resume()
|
||||
* above to bring the suspended devices back to life.
|
||||
*
|
||||
*/
|
||||
|
||||
int device_suspend(pm_message_t state)
|
||||
{
|
||||
int error = 0;
|
||||
|
||||
might_sleep();
|
||||
down(&dpm_sem);
|
||||
down(&dpm_list_sem);
|
||||
while (!list_empty(&dpm_active) && error == 0) {
|
||||
struct list_head * entry = dpm_active.prev;
|
||||
struct device * dev = to_device(entry);
|
||||
|
||||
get_device(dev);
|
||||
up(&dpm_list_sem);
|
||||
|
||||
#ifdef CONFIG_PM_CPU_MODE
|
||||
if(pm_cpu_mode == 0){
|
||||
error = suspend_device(dev, state);
|
||||
}
|
||||
else{
|
||||
// suspend_device(dev, state);
|
||||
}
|
||||
#else
|
||||
error = suspend_device(dev, state);
|
||||
#endif
|
||||
|
||||
down(&dpm_list_sem);
|
||||
|
||||
/* Check if the device got removed */
|
||||
if (!list_empty(&dev->power.entry)) {
|
||||
/* Move it to the dpm_off list */
|
||||
if (!error)
|
||||
list_move(&dev->power.entry, &dpm_off);
|
||||
}
|
||||
if (error)
|
||||
printk(KERN_ERR "Could not suspend device %s: "
|
||||
"error %d%s\n",
|
||||
kobject_name(&dev->kobj), error,
|
||||
error == -EAGAIN ? " (please convert to suspend_late)" : "");
|
||||
put_device(dev);
|
||||
}
|
||||
up(&dpm_list_sem);
|
||||
if (error)
|
||||
dpm_resume();
|
||||
|
||||
up(&dpm_sem);
|
||||
return error;
|
||||
}
|
||||
|
||||
EXPORT_SYMBOL_GPL(device_suspend);
|
||||
|
||||
/**
|
||||
* device_power_down - Shut down special devices.
|
||||
* @state: Power state to enter.
|
||||
*
|
||||
* Walk the dpm_off_irq list, calling ->power_down() for each device that
|
||||
* couldn't power down the device with interrupts enabled. When we're
|
||||
* done, power down system devices.
|
||||
*/
|
||||
|
||||
int device_power_down(pm_message_t state)
|
||||
{
|
||||
int error = 0;
|
||||
struct device * dev;
|
||||
|
||||
while (!list_empty(&dpm_off)) {
|
||||
struct list_head * entry = dpm_off.prev;
|
||||
|
||||
dev = to_device(entry);
|
||||
error = suspend_device_late(dev, state);
|
||||
if (error)
|
||||
goto Error;
|
||||
list_move(&dev->power.entry, &dpm_off_irq);
|
||||
}
|
||||
|
||||
error = sysdev_suspend(state);
|
||||
Done:
|
||||
return error;
|
||||
Error:
|
||||
printk(KERN_ERR "Could not power down device %s: "
|
||||
"error %d\n", kobject_name(&dev->kobj), error);
|
||||
dpm_power_up();
|
||||
goto Done;
|
||||
}
|
||||
|
||||
EXPORT_SYMBOL_GPL(device_power_down);
|
||||
|
||||
void __suspend_report_result(const char *function, void *fn, int ret)
|
||||
{
|
||||
if (ret) {
|
||||
printk(KERN_ERR "%s(): ", function);
|
||||
print_fn_descriptor_symbol("%s() returns ", (unsigned long)fn);
|
||||
printk("%d\n", ret);
|
||||
}
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(__suspend_report_result);
|
||||
165
drivers/base/power/sysfs.c
Normal file
165
drivers/base/power/sysfs.c
Normal file
@@ -0,0 +1,165 @@
|
||||
/*
|
||||
* drivers/base/power/sysfs.c - sysfs entries for device PM
|
||||
*/
|
||||
|
||||
#include <linux/device.h>
|
||||
#include <linux/string.h>
|
||||
#include "power.h"
|
||||
|
||||
|
||||
#ifdef CONFIG_PM_SYSFS_DEPRECATED
|
||||
|
||||
/**
|
||||
* state - Control current power state of device
|
||||
*
|
||||
* show() returns the current power state of the device. '0' indicates
|
||||
* the device is on. Other values (2) indicate the device is in some low
|
||||
* power state.
|
||||
*
|
||||
* store() sets the current power state, which is an integer valued
|
||||
* 0, 2, or 3. Devices with bus.suspend_late(), or bus.resume_early()
|
||||
* methods fail this operation; those methods couldn't be called.
|
||||
* Otherwise,
|
||||
*
|
||||
* - If the recorded dev->power.power_state.event matches the
|
||||
* target value, nothing is done.
|
||||
* - If the recorded event code is nonzero, the device is reactivated
|
||||
* by calling bus.resume() and/or class.resume().
|
||||
* - If the target value is nonzero, the device is suspended by
|
||||
* calling class.suspend() and/or bus.suspend() with event code
|
||||
* PM_EVENT_SUSPEND.
|
||||
*
|
||||
* This mechanism is DEPRECATED and should only be used for testing.
|
||||
*/
|
||||
|
||||
static ssize_t state_show(struct device * dev, struct device_attribute *attr, char * buf)
|
||||
{
|
||||
if (dev->power.power_state.event)
|
||||
return sprintf(buf, "2\n");
|
||||
else
|
||||
return sprintf(buf, "0\n");
|
||||
}
|
||||
|
||||
static ssize_t state_store(struct device * dev, struct device_attribute *attr, const char * buf, size_t n)
|
||||
{
|
||||
pm_message_t state;
|
||||
int error = -EINVAL;
|
||||
|
||||
/* disallow incomplete suspend sequences */
|
||||
if (dev->bus && (dev->bus->suspend_late || dev->bus->resume_early))
|
||||
return error;
|
||||
|
||||
state.event = PM_EVENT_SUSPEND;
|
||||
/* Older apps expected to write "3" here - confused with PCI D3 */
|
||||
if ((n == 1) && !strcmp(buf, "3"))
|
||||
error = dpm_runtime_suspend(dev, state);
|
||||
|
||||
if ((n == 1) && !strcmp(buf, "2"))
|
||||
error = dpm_runtime_suspend(dev, state);
|
||||
|
||||
if ((n == 1) && !strcmp(buf, "0")) {
|
||||
dpm_runtime_resume(dev);
|
||||
error = 0;
|
||||
}
|
||||
|
||||
return error ? error : n;
|
||||
}
|
||||
|
||||
static DEVICE_ATTR(state, 0644, state_show, state_store);
|
||||
|
||||
|
||||
#endif /* CONFIG_PM_SYSFS_DEPRECATED */
|
||||
|
||||
/*
|
||||
* wakeup - Report/change current wakeup option for device
|
||||
*
|
||||
* Some devices support "wakeup" events, which are hardware signals
|
||||
* used to activate devices from suspended or low power states. Such
|
||||
* devices have one of three values for the sysfs power/wakeup file:
|
||||
*
|
||||
* + "enabled\n" to issue the events;
|
||||
* + "disabled\n" not to do so; or
|
||||
* + "\n" for temporary or permanent inability to issue wakeup.
|
||||
*
|
||||
* (For example, unconfigured USB devices can't issue wakeups.)
|
||||
*
|
||||
* Familiar examples of devices that can issue wakeup events include
|
||||
* keyboards and mice (both PS2 and USB styles), power buttons, modems,
|
||||
* "Wake-On-LAN" Ethernet links, GPIO lines, and more. Some events
|
||||
* will wake the entire system from a suspend state; others may just
|
||||
* wake up the device (if the system as a whole is already active).
|
||||
* Some wakeup events use normal IRQ lines; other use special out
|
||||
* of band signaling.
|
||||
*
|
||||
* It is the responsibility of device drivers to enable (or disable)
|
||||
* wakeup signaling as part of changing device power states, respecting
|
||||
* the policy choices provided through the driver model.
|
||||
*
|
||||
* Devices may not be able to generate wakeup events from all power
|
||||
* states. Also, the events may be ignored in some configurations;
|
||||
* for example, they might need help from other devices that aren't
|
||||
* active, or which may have wakeup disabled. Some drivers rely on
|
||||
* wakeup events internally (unless they are disabled), keeping
|
||||
* their hardware in low power modes whenever they're unused. This
|
||||
* saves runtime power, without requiring system-wide sleep states.
|
||||
*/
|
||||
|
||||
static const char enabled[] = "enabled";
|
||||
static const char disabled[] = "disabled";
|
||||
|
||||
static ssize_t
|
||||
wake_show(struct device * dev, struct device_attribute *attr, char * buf)
|
||||
{
|
||||
return sprintf(buf, "%s\n", device_can_wakeup(dev)
|
||||
? (device_may_wakeup(dev) ? enabled : disabled)
|
||||
: "");
|
||||
}
|
||||
|
||||
static ssize_t
|
||||
wake_store(struct device * dev, struct device_attribute *attr,
|
||||
const char * buf, size_t n)
|
||||
{
|
||||
char *cp;
|
||||
int len = n;
|
||||
|
||||
if (!device_can_wakeup(dev))
|
||||
return -EINVAL;
|
||||
|
||||
cp = memchr(buf, '\n', n);
|
||||
if (cp)
|
||||
len = cp - buf;
|
||||
if (len == sizeof enabled - 1
|
||||
&& strncmp(buf, enabled, sizeof enabled - 1) == 0)
|
||||
device_set_wakeup_enable(dev, 1);
|
||||
else if (len == sizeof disabled - 1
|
||||
&& strncmp(buf, disabled, sizeof disabled - 1) == 0)
|
||||
device_set_wakeup_enable(dev, 0);
|
||||
else
|
||||
return -EINVAL;
|
||||
return n;
|
||||
}
|
||||
|
||||
static DEVICE_ATTR(wakeup, 0644, wake_show, wake_store);
|
||||
|
||||
|
||||
static struct attribute * power_attrs[] = {
|
||||
#ifdef CONFIG_PM_SYSFS_DEPRECATED
|
||||
&dev_attr_state.attr,
|
||||
#endif
|
||||
&dev_attr_wakeup.attr,
|
||||
NULL,
|
||||
};
|
||||
static struct attribute_group pm_attr_group = {
|
||||
.name = "power",
|
||||
.attrs = power_attrs,
|
||||
};
|
||||
|
||||
int dpm_sysfs_add(struct device * dev)
|
||||
{
|
||||
return sysfs_create_group(&dev->kobj, &pm_attr_group);
|
||||
}
|
||||
|
||||
void dpm_sysfs_remove(struct device * dev)
|
||||
{
|
||||
sysfs_remove_group(&dev->kobj, &pm_attr_group);
|
||||
}
|
||||
228
drivers/base/power/trace.c
Normal file
228
drivers/base/power/trace.c
Normal file
@@ -0,0 +1,228 @@
|
||||
/*
|
||||
* drivers/base/power/trace.c
|
||||
*
|
||||
* Copyright (C) 2006 Linus Torvalds
|
||||
*
|
||||
* Trace facility for suspend/resume problems, when none of the
|
||||
* devices may be working.
|
||||
*/
|
||||
|
||||
#include <linux/resume-trace.h>
|
||||
#include <linux/rtc.h>
|
||||
|
||||
#include <asm/rtc.h>
|
||||
|
||||
#include "power.h"
|
||||
|
||||
/*
|
||||
* Horrid, horrid, horrid.
|
||||
*
|
||||
* It turns out that the _only_ piece of hardware that actually
|
||||
* keeps its value across a hard boot (and, more importantly, the
|
||||
* POST init sequence) is literally the realtime clock.
|
||||
*
|
||||
* Never mind that an RTC chip has 114 bytes (and often a whole
|
||||
* other bank of an additional 128 bytes) of nice SRAM that is
|
||||
* _designed_ to keep data - the POST will clear it. So we literally
|
||||
* can just use the few bytes of actual time data, which means that
|
||||
* we're really limited.
|
||||
*
|
||||
* It means, for example, that we can't use the seconds at all
|
||||
* (since the time between the hang and the boot might be more
|
||||
* than a minute), and we'd better not depend on the low bits of
|
||||
* the minutes either.
|
||||
*
|
||||
* There are the wday fields etc, but I wouldn't guarantee those
|
||||
* are dependable either. And if the date isn't valid, either the
|
||||
* hw or POST will do strange things.
|
||||
*
|
||||
* So we're left with:
|
||||
* - year: 0-99
|
||||
* - month: 0-11
|
||||
* - day-of-month: 1-28
|
||||
* - hour: 0-23
|
||||
* - min: (0-30)*2
|
||||
*
|
||||
* Giving us a total range of 0-16128000 (0xf61800), ie less
|
||||
* than 24 bits of actual data we can save across reboots.
|
||||
*
|
||||
* And if your box can't boot in less than three minutes,
|
||||
* you're screwed.
|
||||
*
|
||||
* Now, almost 24 bits of data is pitifully small, so we need
|
||||
* to be pretty dense if we want to use it for anything nice.
|
||||
* What we do is that instead of saving off nice readable info,
|
||||
* we save off _hashes_ of information that we can hopefully
|
||||
* regenerate after the reboot.
|
||||
*
|
||||
* In particular, this means that we might be unlucky, and hit
|
||||
* a case where we have a hash collision, and we end up not
|
||||
* being able to tell for certain exactly which case happened.
|
||||
* But that's hopefully unlikely.
|
||||
*
|
||||
* What we do is to take the bits we can fit, and split them
|
||||
* into three parts (16*997*1009 = 16095568), and use the values
|
||||
* for:
|
||||
* - 0-15: user-settable
|
||||
* - 0-996: file + line number
|
||||
* - 0-1008: device
|
||||
*/
|
||||
#define USERHASH (16)
|
||||
#define FILEHASH (997)
|
||||
#define DEVHASH (1009)
|
||||
|
||||
#define DEVSEED (7919)
|
||||
|
||||
static unsigned int dev_hash_value;
|
||||
|
||||
static int set_magic_time(unsigned int user, unsigned int file, unsigned int device)
|
||||
{
|
||||
unsigned int n = user + USERHASH*(file + FILEHASH*device);
|
||||
|
||||
// June 7th, 2006
|
||||
static struct rtc_time time = {
|
||||
.tm_sec = 0,
|
||||
.tm_min = 0,
|
||||
.tm_hour = 0,
|
||||
.tm_mday = 7,
|
||||
.tm_mon = 5, // June - counting from zero
|
||||
.tm_year = 106,
|
||||
.tm_wday = 3,
|
||||
.tm_yday = 160,
|
||||
.tm_isdst = 1
|
||||
};
|
||||
|
||||
time.tm_year = (n % 100);
|
||||
n /= 100;
|
||||
time.tm_mon = (n % 12);
|
||||
n /= 12;
|
||||
time.tm_mday = (n % 28) + 1;
|
||||
n /= 28;
|
||||
time.tm_hour = (n % 24);
|
||||
n /= 24;
|
||||
time.tm_min = (n % 20) * 3;
|
||||
n /= 20;
|
||||
set_rtc_time(&time);
|
||||
return n ? -1 : 0;
|
||||
}
|
||||
|
||||
static unsigned int read_magic_time(void)
|
||||
{
|
||||
struct rtc_time time;
|
||||
unsigned int val;
|
||||
|
||||
get_rtc_time(&time);
|
||||
printk("Time: %2d:%02d:%02d Date: %02d/%02d/%02d\n",
|
||||
time.tm_hour, time.tm_min, time.tm_sec,
|
||||
time.tm_mon, time.tm_mday, time.tm_year);
|
||||
val = time.tm_year; /* 100 years */
|
||||
if (val > 100)
|
||||
val -= 100;
|
||||
val += time.tm_mon * 100; /* 12 months */
|
||||
val += (time.tm_mday-1) * 100 * 12; /* 28 month-days */
|
||||
val += time.tm_hour * 100 * 12 * 28; /* 24 hours */
|
||||
val += (time.tm_min / 3) * 100 * 12 * 28 * 24; /* 20 3-minute intervals */
|
||||
return val;
|
||||
}
|
||||
|
||||
/*
|
||||
* This is just the sdbm hash function with a user-supplied
|
||||
* seed and final size parameter.
|
||||
*/
|
||||
static unsigned int hash_string(unsigned int seed, const char *data, unsigned int mod)
|
||||
{
|
||||
unsigned char c;
|
||||
while ((c = *data++) != 0) {
|
||||
seed = (seed << 16) + (seed << 6) - seed + c;
|
||||
}
|
||||
return seed % mod;
|
||||
}
|
||||
|
||||
void set_trace_device(struct device *dev)
|
||||
{
|
||||
dev_hash_value = hash_string(DEVSEED, dev->bus_id, DEVHASH);
|
||||
}
|
||||
|
||||
/*
|
||||
* We could just take the "tracedata" index into the .tracedata
|
||||
* section instead. Generating a hash of the data gives us a
|
||||
* chance to work across kernel versions, and perhaps more
|
||||
* importantly it also gives us valid/invalid check (ie we will
|
||||
* likely not give totally bogus reports - if the hash matches,
|
||||
* it's not any guarantee, but it's a high _likelihood_ that
|
||||
* the match is valid).
|
||||
*/
|
||||
void generate_resume_trace(void *tracedata, unsigned int user)
|
||||
{
|
||||
unsigned short lineno = *(unsigned short *)tracedata;
|
||||
const char *file = *(const char **)(tracedata + 2);
|
||||
unsigned int user_hash_value, file_hash_value;
|
||||
|
||||
user_hash_value = user % USERHASH;
|
||||
file_hash_value = hash_string(lineno, file, FILEHASH);
|
||||
set_magic_time(user_hash_value, file_hash_value, dev_hash_value);
|
||||
}
|
||||
|
||||
extern char __tracedata_start, __tracedata_end;
|
||||
static int show_file_hash(unsigned int value)
|
||||
{
|
||||
int match;
|
||||
char *tracedata;
|
||||
|
||||
match = 0;
|
||||
for (tracedata = &__tracedata_start ; tracedata < &__tracedata_end ; tracedata += 6) {
|
||||
unsigned short lineno = *(unsigned short *)tracedata;
|
||||
const char *file = *(const char **)(tracedata + 2);
|
||||
unsigned int hash = hash_string(lineno, file, FILEHASH);
|
||||
if (hash != value)
|
||||
continue;
|
||||
printk(" hash matches %s:%u\n", file, lineno);
|
||||
match++;
|
||||
}
|
||||
return match;
|
||||
}
|
||||
|
||||
static int show_dev_hash(unsigned int value)
|
||||
{
|
||||
int match = 0;
|
||||
struct list_head * entry = dpm_active.prev;
|
||||
|
||||
while (entry != &dpm_active) {
|
||||
struct device * dev = to_device(entry);
|
||||
unsigned int hash = hash_string(DEVSEED, dev->bus_id, DEVHASH);
|
||||
if (hash == value) {
|
||||
printk(" hash matches device %s\n", dev->bus_id);
|
||||
match++;
|
||||
}
|
||||
entry = entry->prev;
|
||||
}
|
||||
return match;
|
||||
}
|
||||
|
||||
static unsigned int hash_value_early_read;
|
||||
|
||||
static int early_resume_init(void)
|
||||
{
|
||||
hash_value_early_read = read_magic_time();
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int late_resume_init(void)
|
||||
{
|
||||
unsigned int val = hash_value_early_read;
|
||||
unsigned int user, file, dev;
|
||||
|
||||
user = val % USERHASH;
|
||||
val = val / USERHASH;
|
||||
file = val % FILEHASH;
|
||||
val = val / FILEHASH;
|
||||
dev = val /* % DEVHASH */;
|
||||
|
||||
printk(" Magic number: %d:%d:%d\n", user, file, dev);
|
||||
show_file_hash(file);
|
||||
show_dev_hash(dev);
|
||||
return 0;
|
||||
}
|
||||
|
||||
core_initcall(early_resume_init);
|
||||
late_initcall(late_resume_init);
|
||||
Reference in New Issue
Block a user