diff options
-rw-r--r-- | drivers/base/power/main.c | 115 | ||||
-rw-r--r-- | include/linux/device.h | 6 | ||||
-rw-r--r-- | include/linux/pm.h | 3 | ||||
-rw-r--r-- | include/linux/resume-trace.h | 7 |
4 files changed, 125 insertions, 6 deletions
diff --git a/drivers/base/power/main.c b/drivers/base/power/main.c index 6ca5cdf63849..3b44c201ddad 100644 --- a/drivers/base/power/main.c +++ b/drivers/base/power/main.c | |||
@@ -25,6 +25,7 @@ | |||
25 | #include <linux/resume-trace.h> | 25 | #include <linux/resume-trace.h> |
26 | #include <linux/interrupt.h> | 26 | #include <linux/interrupt.h> |
27 | #include <linux/sched.h> | 27 | #include <linux/sched.h> |
28 | #include <linux/async.h> | ||
28 | 29 | ||
29 | #include "../base.h" | 30 | #include "../base.h" |
30 | #include "power.h" | 31 | #include "power.h" |
@@ -42,6 +43,7 @@ | |||
42 | LIST_HEAD(dpm_list); | 43 | LIST_HEAD(dpm_list); |
43 | 44 | ||
44 | static DEFINE_MUTEX(dpm_list_mtx); | 45 | static DEFINE_MUTEX(dpm_list_mtx); |
46 | static pm_message_t pm_transition; | ||
45 | 47 | ||
46 | /* | 48 | /* |
47 | * Set once the preparation of devices for a PM transition has started, reset | 49 | * Set once the preparation of devices for a PM transition has started, reset |
@@ -56,6 +58,7 @@ static bool transition_started; | |||
56 | void device_pm_init(struct device *dev) | 58 | void device_pm_init(struct device *dev) |
57 | { | 59 | { |
58 | dev->power.status = DPM_ON; | 60 | dev->power.status = DPM_ON; |
61 | init_completion(&dev->power.completion); | ||
59 | pm_runtime_init(dev); | 62 | pm_runtime_init(dev); |
60 | } | 63 | } |
61 | 64 | ||
@@ -111,6 +114,7 @@ void device_pm_remove(struct device *dev) | |||
111 | pr_debug("PM: Removing info for %s:%s\n", | 114 | pr_debug("PM: Removing info for %s:%s\n", |
112 | dev->bus ? dev->bus->name : "No Bus", | 115 | dev->bus ? dev->bus->name : "No Bus", |
113 | kobject_name(&dev->kobj)); | 116 | kobject_name(&dev->kobj)); |
117 | complete_all(&dev->power.completion); | ||
114 | mutex_lock(&dpm_list_mtx); | 118 | mutex_lock(&dpm_list_mtx); |
115 | list_del_init(&dev->power.entry); | 119 | list_del_init(&dev->power.entry); |
116 | mutex_unlock(&dpm_list_mtx); | 120 | mutex_unlock(&dpm_list_mtx); |
@@ -188,6 +192,31 @@ static void initcall_debug_report(struct device *dev, ktime_t calltime, | |||
188 | } | 192 | } |
189 | 193 | ||
190 | /** | 194 | /** |
195 | * dpm_wait - Wait for a PM operation to complete. | ||
196 | * @dev: Device to wait for. | ||
197 | * @async: If unset, wait only if the device's power.async_suspend flag is set. | ||
198 | */ | ||
199 | static void dpm_wait(struct device *dev, bool async) | ||
200 | { | ||
201 | if (!dev) | ||
202 | return; | ||
203 | |||
204 | if (async || dev->power.async_suspend) | ||
205 | wait_for_completion(&dev->power.completion); | ||
206 | } | ||
207 | |||
208 | static int dpm_wait_fn(struct device *dev, void *async_ptr) | ||
209 | { | ||
210 | dpm_wait(dev, *((bool *)async_ptr)); | ||
211 | return 0; | ||
212 | } | ||
213 | |||
214 | static void dpm_wait_for_children(struct device *dev, bool async) | ||
215 | { | ||
216 | device_for_each_child(dev, &async, dpm_wait_fn); | ||
217 | } | ||
218 | |||
219 | /** | ||
191 | * pm_op - Execute the PM operation appropriate for given PM event. | 220 | * pm_op - Execute the PM operation appropriate for given PM event. |
192 | * @dev: Device to handle. | 221 | * @dev: Device to handle. |
193 | * @ops: PM operations to choose from. | 222 | * @ops: PM operations to choose from. |
@@ -466,17 +495,19 @@ static int legacy_resume(struct device *dev, int (*cb)(struct device *dev)) | |||
466 | } | 495 | } |
467 | 496 | ||
468 | /** | 497 | /** |
469 | * device_resume - Execute "resume" callbacks for given device. | 498 | * __device_resume - Execute "resume" callbacks for given device. |
470 | * @dev: Device to handle. | 499 | * @dev: Device to handle. |
471 | * @state: PM transition of the system being carried out. | 500 | * @state: PM transition of the system being carried out. |
501 | * @async: If true, the device is being resumed asynchronously. | ||
472 | */ | 502 | */ |
473 | static int device_resume(struct device *dev, pm_message_t state) | 503 | static int __device_resume(struct device *dev, pm_message_t state, bool async) |
474 | { | 504 | { |
475 | int error = 0; | 505 | int error = 0; |
476 | 506 | ||
477 | TRACE_DEVICE(dev); | 507 | TRACE_DEVICE(dev); |
478 | TRACE_RESUME(0); | 508 | TRACE_RESUME(0); |
479 | 509 | ||
510 | dpm_wait(dev->parent, async); | ||
480 | down(&dev->sem); | 511 | down(&dev->sem); |
481 | 512 | ||
482 | if (dev->bus) { | 513 | if (dev->bus) { |
@@ -511,11 +542,36 @@ static int device_resume(struct device *dev, pm_message_t state) | |||
511 | } | 542 | } |
512 | End: | 543 | End: |
513 | up(&dev->sem); | 544 | up(&dev->sem); |
545 | complete_all(&dev->power.completion); | ||
514 | 546 | ||
515 | TRACE_RESUME(error); | 547 | TRACE_RESUME(error); |
516 | return error; | 548 | return error; |
517 | } | 549 | } |
518 | 550 | ||
551 | static void async_resume(void *data, async_cookie_t cookie) | ||
552 | { | ||
553 | struct device *dev = (struct device *)data; | ||
554 | int error; | ||
555 | |||
556 | error = __device_resume(dev, pm_transition, true); | ||
557 | if (error) | ||
558 | pm_dev_err(dev, pm_transition, " async", error); | ||
559 | put_device(dev); | ||
560 | } | ||
561 | |||
562 | static int device_resume(struct device *dev) | ||
563 | { | ||
564 | INIT_COMPLETION(dev->power.completion); | ||
565 | |||
566 | if (dev->power.async_suspend && !pm_trace_is_enabled()) { | ||
567 | get_device(dev); | ||
568 | async_schedule(async_resume, dev); | ||
569 | return 0; | ||
570 | } | ||
571 | |||
572 | return __device_resume(dev, pm_transition, false); | ||
573 | } | ||
574 | |||
519 | /** | 575 | /** |
520 | * dpm_resume - Execute "resume" callbacks for non-sysdev devices. | 576 | * dpm_resume - Execute "resume" callbacks for non-sysdev devices. |
521 | * @state: PM transition of the system being carried out. | 577 | * @state: PM transition of the system being carried out. |
@@ -530,6 +586,7 @@ static void dpm_resume(pm_message_t state) | |||
530 | 586 | ||
531 | INIT_LIST_HEAD(&list); | 587 | INIT_LIST_HEAD(&list); |
532 | mutex_lock(&dpm_list_mtx); | 588 | mutex_lock(&dpm_list_mtx); |
589 | pm_transition = state; | ||
533 | while (!list_empty(&dpm_list)) { | 590 | while (!list_empty(&dpm_list)) { |
534 | struct device *dev = to_device(dpm_list.next); | 591 | struct device *dev = to_device(dpm_list.next); |
535 | 592 | ||
@@ -540,7 +597,7 @@ static void dpm_resume(pm_message_t state) | |||
540 | dev->power.status = DPM_RESUMING; | 597 | dev->power.status = DPM_RESUMING; |
541 | mutex_unlock(&dpm_list_mtx); | 598 | mutex_unlock(&dpm_list_mtx); |
542 | 599 | ||
543 | error = device_resume(dev, state); | 600 | error = device_resume(dev); |
544 | 601 | ||
545 | mutex_lock(&dpm_list_mtx); | 602 | mutex_lock(&dpm_list_mtx); |
546 | if (error) | 603 | if (error) |
@@ -555,6 +612,7 @@ static void dpm_resume(pm_message_t state) | |||
555 | } | 612 | } |
556 | list_splice(&list, &dpm_list); | 613 | list_splice(&list, &dpm_list); |
557 | mutex_unlock(&dpm_list_mtx); | 614 | mutex_unlock(&dpm_list_mtx); |
615 | async_synchronize_full(); | ||
558 | dpm_show_time(starttime, state, NULL); | 616 | dpm_show_time(starttime, state, NULL); |
559 | } | 617 | } |
560 | 618 | ||
@@ -732,17 +790,24 @@ static int legacy_suspend(struct device *dev, pm_message_t state, | |||
732 | return error; | 790 | return error; |
733 | } | 791 | } |
734 | 792 | ||
793 | static int async_error; | ||
794 | |||
735 | /** | 795 | /** |
736 | * device_suspend - Execute "suspend" callbacks for given device. | 796 | * device_suspend - Execute "suspend" callbacks for given device. |
737 | * @dev: Device to handle. | 797 | * @dev: Device to handle. |
738 | * @state: PM transition of the system being carried out. | 798 | * @state: PM transition of the system being carried out. |
799 | * @async: If true, the device is being suspended asynchronously. | ||
739 | */ | 800 | */ |
740 | static int device_suspend(struct device *dev, pm_message_t state) | 801 | static int __device_suspend(struct device *dev, pm_message_t state, bool async) |
741 | { | 802 | { |
742 | int error = 0; | 803 | int error = 0; |
743 | 804 | ||
805 | dpm_wait_for_children(dev, async); | ||
744 | down(&dev->sem); | 806 | down(&dev->sem); |
745 | 807 | ||
808 | if (async_error) | ||
809 | goto End; | ||
810 | |||
746 | if (dev->class) { | 811 | if (dev->class) { |
747 | if (dev->class->pm) { | 812 | if (dev->class->pm) { |
748 | pm_dev_dbg(dev, state, "class "); | 813 | pm_dev_dbg(dev, state, "class "); |
@@ -773,12 +838,44 @@ static int device_suspend(struct device *dev, pm_message_t state) | |||
773 | error = legacy_suspend(dev, state, dev->bus->suspend); | 838 | error = legacy_suspend(dev, state, dev->bus->suspend); |
774 | } | 839 | } |
775 | } | 840 | } |
841 | |||
842 | if (!error) | ||
843 | dev->power.status = DPM_OFF; | ||
844 | |||
776 | End: | 845 | End: |
777 | up(&dev->sem); | 846 | up(&dev->sem); |
847 | complete_all(&dev->power.completion); | ||
778 | 848 | ||
779 | return error; | 849 | return error; |
780 | } | 850 | } |
781 | 851 | ||
852 | static void async_suspend(void *data, async_cookie_t cookie) | ||
853 | { | ||
854 | struct device *dev = (struct device *)data; | ||
855 | int error; | ||
856 | |||
857 | error = __device_suspend(dev, pm_transition, true); | ||
858 | if (error) { | ||
859 | pm_dev_err(dev, pm_transition, " async", error); | ||
860 | async_error = error; | ||
861 | } | ||
862 | |||
863 | put_device(dev); | ||
864 | } | ||
865 | |||
866 | static int device_suspend(struct device *dev) | ||
867 | { | ||
868 | INIT_COMPLETION(dev->power.completion); | ||
869 | |||
870 | if (dev->power.async_suspend) { | ||
871 | get_device(dev); | ||
872 | async_schedule(async_suspend, dev); | ||
873 | return 0; | ||
874 | } | ||
875 | |||
876 | return __device_suspend(dev, pm_transition, false); | ||
877 | } | ||
878 | |||
782 | /** | 879 | /** |
783 | * dpm_suspend - Execute "suspend" callbacks for all non-sysdev devices. | 880 | * dpm_suspend - Execute "suspend" callbacks for all non-sysdev devices. |
784 | * @state: PM transition of the system being carried out. | 881 | * @state: PM transition of the system being carried out. |
@@ -791,13 +888,15 @@ static int dpm_suspend(pm_message_t state) | |||
791 | 888 | ||
792 | INIT_LIST_HEAD(&list); | 889 | INIT_LIST_HEAD(&list); |
793 | mutex_lock(&dpm_list_mtx); | 890 | mutex_lock(&dpm_list_mtx); |
891 | pm_transition = state; | ||
892 | async_error = 0; | ||
794 | while (!list_empty(&dpm_list)) { | 893 | while (!list_empty(&dpm_list)) { |
795 | struct device *dev = to_device(dpm_list.prev); | 894 | struct device *dev = to_device(dpm_list.prev); |
796 | 895 | ||
797 | get_device(dev); | 896 | get_device(dev); |
798 | mutex_unlock(&dpm_list_mtx); | 897 | mutex_unlock(&dpm_list_mtx); |
799 | 898 | ||
800 | error = device_suspend(dev, state); | 899 | error = device_suspend(dev); |
801 | 900 | ||
802 | mutex_lock(&dpm_list_mtx); | 901 | mutex_lock(&dpm_list_mtx); |
803 | if (error) { | 902 | if (error) { |
@@ -805,13 +904,17 @@ static int dpm_suspend(pm_message_t state) | |||
805 | put_device(dev); | 904 | put_device(dev); |
806 | break; | 905 | break; |
807 | } | 906 | } |
808 | dev->power.status = DPM_OFF; | ||
809 | if (!list_empty(&dev->power.entry)) | 907 | if (!list_empty(&dev->power.entry)) |
810 | list_move(&dev->power.entry, &list); | 908 | list_move(&dev->power.entry, &list); |
811 | put_device(dev); | 909 | put_device(dev); |
910 | if (async_error) | ||
911 | break; | ||
812 | } | 912 | } |
813 | list_splice(&list, dpm_list.prev); | 913 | list_splice(&list, dpm_list.prev); |
814 | mutex_unlock(&dpm_list_mtx); | 914 | mutex_unlock(&dpm_list_mtx); |
915 | async_synchronize_full(); | ||
916 | if (!error) | ||
917 | error = async_error; | ||
815 | if (!error) | 918 | if (!error) |
816 | dpm_show_time(starttime, state, NULL); | 919 | dpm_show_time(starttime, state, NULL); |
817 | return error; | 920 | return error; |
diff --git a/include/linux/device.h b/include/linux/device.h index a62799f2ab00..70adc5f3f50a 100644 --- a/include/linux/device.h +++ b/include/linux/device.h | |||
@@ -472,6 +472,12 @@ static inline int device_is_registered(struct device *dev) | |||
472 | return dev->kobj.state_in_sysfs; | 472 | return dev->kobj.state_in_sysfs; |
473 | } | 473 | } |
474 | 474 | ||
475 | static inline void device_enable_async_suspend(struct device *dev) | ||
476 | { | ||
477 | if (dev->power.status == DPM_ON) | ||
478 | dev->power.async_suspend = true; | ||
479 | } | ||
480 | |||
475 | void driver_init(void); | 481 | void driver_init(void); |
476 | 482 | ||
477 | /* | 483 | /* |
diff --git a/include/linux/pm.h b/include/linux/pm.h index 25b1eca8049d..9c16cd20fc96 100644 --- a/include/linux/pm.h +++ b/include/linux/pm.h | |||
@@ -26,6 +26,7 @@ | |||
26 | #include <linux/spinlock.h> | 26 | #include <linux/spinlock.h> |
27 | #include <linux/wait.h> | 27 | #include <linux/wait.h> |
28 | #include <linux/timer.h> | 28 | #include <linux/timer.h> |
29 | #include <linux/completion.h> | ||
29 | 30 | ||
30 | /* | 31 | /* |
31 | * Callbacks for platform drivers to implement. | 32 | * Callbacks for platform drivers to implement. |
@@ -412,9 +413,11 @@ struct dev_pm_info { | |||
412 | pm_message_t power_state; | 413 | pm_message_t power_state; |
413 | unsigned int can_wakeup:1; | 414 | unsigned int can_wakeup:1; |
414 | unsigned int should_wakeup:1; | 415 | unsigned int should_wakeup:1; |
416 | unsigned async_suspend:1; | ||
415 | enum dpm_state status; /* Owned by the PM core */ | 417 | enum dpm_state status; /* Owned by the PM core */ |
416 | #ifdef CONFIG_PM_SLEEP | 418 | #ifdef CONFIG_PM_SLEEP |
417 | struct list_head entry; | 419 | struct list_head entry; |
420 | struct completion completion; | ||
418 | #endif | 421 | #endif |
419 | #ifdef CONFIG_PM_RUNTIME | 422 | #ifdef CONFIG_PM_RUNTIME |
420 | struct timer_list suspend_timer; | 423 | struct timer_list suspend_timer; |
diff --git a/include/linux/resume-trace.h b/include/linux/resume-trace.h index c9ba2fdf807d..bc8c3881c729 100644 --- a/include/linux/resume-trace.h +++ b/include/linux/resume-trace.h | |||
@@ -6,6 +6,11 @@ | |||
6 | 6 | ||
7 | extern int pm_trace_enabled; | 7 | extern int pm_trace_enabled; |
8 | 8 | ||
9 | static inline int pm_trace_is_enabled(void) | ||
10 | { | ||
11 | return pm_trace_enabled; | ||
12 | } | ||
13 | |||
9 | struct device; | 14 | struct device; |
10 | extern void set_trace_device(struct device *); | 15 | extern void set_trace_device(struct device *); |
11 | extern void generate_resume_trace(const void *tracedata, unsigned int user); | 16 | extern void generate_resume_trace(const void *tracedata, unsigned int user); |
@@ -17,6 +22,8 @@ extern void generate_resume_trace(const void *tracedata, unsigned int user); | |||
17 | 22 | ||
18 | #else | 23 | #else |
19 | 24 | ||
25 | static inline int pm_trace_is_enabled(void) { return 0; } | ||
26 | |||
20 | #define TRACE_DEVICE(dev) do { } while (0) | 27 | #define TRACE_DEVICE(dev) do { } while (0) |
21 | #define TRACE_RESUME(dev) do { } while (0) | 28 | #define TRACE_RESUME(dev) do { } while (0) |
22 | 29 | ||