diff options
author | codeworkx <daniel.hillenbrand@codeworkx.de> | 2012-06-02 13:09:29 +0200 |
---|---|---|
committer | codeworkx <daniel.hillenbrand@codeworkx.de> | 2012-06-02 13:09:29 +0200 |
commit | c6da2cfeb05178a11c6d062a06f8078150ee492f (patch) | |
tree | f3b4021d252c52d6463a9b3c1bb7245e399b009c /drivers/base/power | |
parent | c6d7c4dbff353eac7919342ae6b3299a378160a6 (diff) | |
download | kernel_samsung_smdk4412-c6da2cfeb05178a11c6d062a06f8078150ee492f.zip kernel_samsung_smdk4412-c6da2cfeb05178a11c6d062a06f8078150ee492f.tar.gz kernel_samsung_smdk4412-c6da2cfeb05178a11c6d062a06f8078150ee492f.tar.bz2 |
samsung update 1
Diffstat (limited to 'drivers/base/power')
-rw-r--r-- | drivers/base/power/main.c | 102 | ||||
-rw-r--r-- | drivers/base/power/opp.c | 32 | ||||
-rw-r--r-- | drivers/base/power/runtime.c | 9 |
3 files changed, 136 insertions, 7 deletions
diff --git a/drivers/base/power/main.c b/drivers/base/power/main.c index 06f09bf..cc696d6 100644 --- a/drivers/base/power/main.c +++ b/drivers/base/power/main.c @@ -27,6 +27,7 @@ #include <linux/sched.h> #include <linux/async.h> #include <linux/suspend.h> +#include <linux/timer.h> #include "../base.h" #include "power.h" @@ -49,6 +50,12 @@ LIST_HEAD(dpm_noirq_list); static DEFINE_MUTEX(dpm_list_mtx); static pm_message_t pm_transition; +static void dpm_drv_timeout(unsigned long data); +struct dpm_drv_wd_data { + struct device *dev; + struct task_struct *tsk; +}; + static int async_error; /** @@ -421,6 +428,16 @@ static void dpm_show_time(ktime_t starttime, pm_message_t state, char *info) static int device_resume_noirq(struct device *dev, pm_message_t state) { int error = 0; + struct timer_list timer; + struct dpm_drv_wd_data data; + + data.dev = dev; + data.tsk = get_current(); + init_timer_on_stack(&timer); + timer.expires = jiffies + HZ * 12; + timer.function = dpm_drv_timeout; + timer.data = (unsigned long)&data; + add_timer(&timer); TRACE_DEVICE(dev); TRACE_RESUME(0); @@ -439,6 +456,9 @@ static int device_resume_noirq(struct device *dev, pm_message_t state) error = pm_noirq_op(dev, dev->bus->pm, state); } + del_timer_sync(&timer); + destroy_timer_on_stack(&timer); + TRACE_RESUME(error); return error; } @@ -505,11 +525,22 @@ static int legacy_resume(struct device *dev, int (*cb)(struct device *dev)) static int device_resume(struct device *dev, pm_message_t state, bool async) { int error = 0; + struct timer_list timer; + struct dpm_drv_wd_data data; TRACE_DEVICE(dev); TRACE_RESUME(0); dpm_wait(dev->parent, async); + + data.dev = dev; + data.tsk = get_current(); + init_timer_on_stack(&timer); + timer.expires = jiffies + HZ * 12; + timer.function = dpm_drv_timeout; + timer.data = (unsigned long)&data; + add_timer(&timer); + device_lock(dev); /* @@ -560,6 +591,10 @@ static int device_resume(struct device *dev, pm_message_t state, bool async) Unlock: device_unlock(dev); + + del_timer_sync(&timer); + destroy_timer_on_stack(&timer); + complete_all(&dev->power.completion); TRACE_RESUME(error); @@ -584,6 +619,30 @@ static bool is_async(struct device *dev) } /** + * dpm_drv_timeout - Driver suspend / resume watchdog handler + * @data: struct device which timed out + * + * Called when a driver has timed out suspending or resuming. + * There's not much we can do here to recover so + * BUG() out for a crash-dump + * + */ +static void dpm_drv_timeout(unsigned long data) +{ + struct dpm_drv_wd_data *wd_data = (void *)data; + struct device *dev = wd_data->dev; + struct task_struct *tsk = wd_data->tsk; + + printk(KERN_EMERG "**** DPM device timeout: %s (%s)\n", dev_name(dev), + (dev->driver ? dev->driver->name : "no driver")); + + printk(KERN_EMERG "dpm suspend stack:\n"); + show_stack(tsk, NULL); + + BUG(); +} + +/** * dpm_resume - Execute "resume" callbacks for non-sysdev devices. * @state: PM transition of the system being carried out. * @@ -742,31 +801,45 @@ static pm_message_t resume_event(pm_message_t sleep_state) */ static int device_suspend_noirq(struct device *dev, pm_message_t state) { - int error; + int error = 0; + struct timer_list timer; + struct dpm_drv_wd_data data; + + data.dev = dev; + data.tsk = get_current(); + init_timer_on_stack(&timer); + timer.expires = jiffies + HZ * 12; + timer.function = dpm_drv_timeout; + timer.data = (unsigned long)&data; + add_timer(&timer); if (dev->pwr_domain) { pm_dev_dbg(dev, state, "LATE power domain "); error = pm_noirq_op(dev, &dev->pwr_domain->ops, state); if (error) - return error; + goto exit; } else if (dev->type && dev->type->pm) { pm_dev_dbg(dev, state, "LATE type "); error = pm_noirq_op(dev, dev->type->pm, state); if (error) - return error; + goto exit; } else if (dev->class && dev->class->pm) { pm_dev_dbg(dev, state, "LATE class "); error = pm_noirq_op(dev, dev->class->pm, state); if (error) - return error; + goto exit; } else if (dev->bus && dev->bus->pm) { pm_dev_dbg(dev, state, "LATE "); error = pm_noirq_op(dev, dev->bus->pm, state); if (error) - return error; + goto exit; } - return 0; +exit: + del_timer_sync(&timer); + destroy_timer_on_stack(&timer); + + return error; } /** @@ -841,17 +914,30 @@ static int legacy_suspend(struct device *dev, pm_message_t state, static int __device_suspend(struct device *dev, pm_message_t state, bool async) { int error = 0; + struct timer_list timer; + struct dpm_drv_wd_data data; dpm_wait_for_children(dev, async); + + data.dev = dev; + data.tsk = get_current(); + init_timer_on_stack(&timer); + timer.expires = jiffies + HZ * 12; + timer.function = dpm_drv_timeout; + timer.data = (unsigned long)&data; + add_timer(&timer); + device_lock(dev); if (async_error) goto Unlock; +#ifndef CONFIG_SLP if (pm_wakeup_pending()) { async_error = -EBUSY; goto Unlock; } +#endif if (dev->pwr_domain) { pm_dev_dbg(dev, state, "power domain "); @@ -892,6 +978,10 @@ static int __device_suspend(struct device *dev, pm_message_t state, bool async) Unlock: device_unlock(dev); + + del_timer_sync(&timer); + destroy_timer_on_stack(&timer); + complete_all(&dev->power.completion); if (error) diff --git a/drivers/base/power/opp.c b/drivers/base/power/opp.c index 56a6899..335e9dd 100644 --- a/drivers/base/power/opp.c +++ b/drivers/base/power/opp.c @@ -73,6 +73,7 @@ struct opp { * RCU usage: nodes are not modified in the list of device_opp, * however addition is possible and is secured by dev_opp_list_lock * @dev: device pointer + * @head: notifier head to notify the OPP availability changes. * @opp_list: list of opps * * This is an internal data structure maintaining the link to opps attached to @@ -83,6 +84,7 @@ struct device_opp { struct list_head node; struct device *dev; + struct srcu_notifier_head head; struct list_head opp_list; }; @@ -404,6 +406,7 @@ int opp_add(struct device *dev, unsigned long freq, unsigned long u_volt) } dev_opp->dev = dev; + srcu_init_notifier_head(&dev_opp->head); INIT_LIST_HEAD(&dev_opp->opp_list); /* Secure the device list modification */ @@ -428,6 +431,11 @@ int opp_add(struct device *dev, unsigned long freq, unsigned long u_volt) list_add_rcu(&new_opp->node, head); mutex_unlock(&dev_opp_list_lock); + /* + * Notify the changes in the availability of the operable + * frequency/voltage list. + */ + srcu_notifier_call_chain(&dev_opp->head, OPP_EVENT_ADD, new_opp); return 0; } @@ -453,7 +461,7 @@ int opp_add(struct device *dev, unsigned long freq, unsigned long u_volt) static int opp_set_availability(struct device *dev, unsigned long freq, bool availability_req) { - struct device_opp *tmp_dev_opp, *dev_opp = NULL; + struct device_opp *tmp_dev_opp, *dev_opp = ERR_PTR(-ENODEV); struct opp *new_opp, *tmp_opp, *opp = ERR_PTR(-ENODEV); int r = 0; @@ -504,6 +512,14 @@ static int opp_set_availability(struct device *dev, unsigned long freq, mutex_unlock(&dev_opp_list_lock); synchronize_rcu(); + /* Notify the change of the OPP availability */ + if (availability_req) + srcu_notifier_call_chain(&dev_opp->head, OPP_EVENT_ENABLE, + new_opp); + else + srcu_notifier_call_chain(&dev_opp->head, OPP_EVENT_DISABLE, + new_opp); + /* clean up old opp */ new_opp = opp; goto out; @@ -626,3 +642,17 @@ int opp_init_cpufreq_table(struct device *dev, return 0; } #endif /* CONFIG_CPU_FREQ */ + +/** + * opp_get_notifier() - find notifier_head of the device with opp + * @dev: device pointer used to lookup device OPPs. + */ +struct srcu_notifier_head *opp_get_notifier(struct device *dev) +{ + struct device_opp *dev_opp = find_device_opp(dev); + + if (IS_ERR(dev_opp)) + return ERR_CAST(dev_opp); /* matching type */ + + return &dev_opp->head; +} diff --git a/drivers/base/power/runtime.c b/drivers/base/power/runtime.c index 1023392..184cf54 100644 --- a/drivers/base/power/runtime.c +++ b/drivers/base/power/runtime.c @@ -746,6 +746,8 @@ int __pm_runtime_idle(struct device *dev, int rpmflags) unsigned long flags; int retval; + might_sleep_if(!(rpmflags & RPM_ASYNC)); + if (rpmflags & RPM_GET_PUT) { if (!atomic_dec_and_test(&dev->power.usage_count)) return 0; @@ -775,6 +777,8 @@ int __pm_runtime_suspend(struct device *dev, int rpmflags) unsigned long flags; int retval; + might_sleep_if(!(rpmflags & RPM_ASYNC) && !dev->power.irq_safe); + if (rpmflags & RPM_GET_PUT) { if (!atomic_dec_and_test(&dev->power.usage_count)) return 0; @@ -803,6 +807,8 @@ int __pm_runtime_resume(struct device *dev, int rpmflags) unsigned long flags; int retval; + might_sleep_if(!(rpmflags & RPM_ASYNC) && !dev->power.irq_safe); + if (rpmflags & RPM_GET_PUT) atomic_inc(&dev->power.usage_count); @@ -992,6 +998,7 @@ EXPORT_SYMBOL_GPL(pm_runtime_barrier); */ void __pm_runtime_disable(struct device *dev, bool check_resume) { + might_sleep(); spin_lock_irq(&dev->power.lock); if (dev->power.disable_depth > 0) { @@ -1198,6 +1205,8 @@ void __pm_runtime_use_autosuspend(struct device *dev, bool use) { int old_delay, old_use; + might_sleep(); + spin_lock_irq(&dev->power.lock); old_delay = dev->power.autosuspend_delay; old_use = dev->power.use_autosuspend; |