diff options
| author | Rafael J. Wysocki <rjw@sisk.pl> | 2011-11-30 18:02:10 -0500 |
|---|---|---|
| committer | Rafael J. Wysocki <rjw@sisk.pl> | 2011-12-01 15:47:58 -0500 |
| commit | 221e9b58380abdd6c05e11b4538597e2586ee141 (patch) | |
| tree | acc8e06b6f05050f5deec0c90e6fe63a8a09bdf1 | |
| parent | b02c999ac325e977585abeb4caf6e0a2ee21e30b (diff) | |
PM / Domains: Add default power off governor function (v4)
Add a function deciding whether or not a given PM domain should
be powered off on the basis of the PM QoS constraints of devices
belonging to it and their PM QoS timing data.
Signed-off-by: Rafael J. Wysocki <rjw@sisk.pl>
| -rw-r--r-- | drivers/base/power/domain.c | 12 | ||||
| -rw-r--r-- | drivers/base/power/domain_governor.c | 110 | ||||
| -rw-r--r-- | include/linux/pm_domain.h | 7 |
3 files changed, 129 insertions, 0 deletions
diff --git a/drivers/base/power/domain.c b/drivers/base/power/domain.c index 3af9f5a71ad5..91896194e76b 100644 --- a/drivers/base/power/domain.c +++ b/drivers/base/power/domain.c | |||
| @@ -398,6 +398,17 @@ static int pm_genpd_poweroff(struct generic_pm_domain *genpd) | |||
| 398 | } | 398 | } |
| 399 | 399 | ||
| 400 | genpd->status = GPD_STATE_POWER_OFF; | 400 | genpd->status = GPD_STATE_POWER_OFF; |
| 401 | genpd->power_off_time = ktime_get(); | ||
| 402 | |||
| 403 | /* Update PM QoS information for devices in the domain. */ | ||
| 404 | list_for_each_entry_reverse(pdd, &genpd->dev_list, list_node) { | ||
| 405 | struct gpd_timing_data *td = &to_gpd_data(pdd)->td; | ||
| 406 | |||
| 407 | pm_runtime_update_max_time_suspended(pdd->dev, | ||
| 408 | td->start_latency_ns + | ||
| 409 | td->restore_state_latency_ns + | ||
| 410 | genpd->power_on_latency_ns); | ||
| 411 | } | ||
| 401 | 412 | ||
| 402 | list_for_each_entry(link, &genpd->slave_links, slave_node) { | 413 | list_for_each_entry(link, &genpd->slave_links, slave_node) { |
| 403 | genpd_sd_counter_dec(link->master); | 414 | genpd_sd_counter_dec(link->master); |
| @@ -1487,6 +1498,7 @@ void pm_genpd_init(struct generic_pm_domain *genpd, | |||
| 1487 | genpd->resume_count = 0; | 1498 | genpd->resume_count = 0; |
| 1488 | genpd->device_count = 0; | 1499 | genpd->device_count = 0; |
| 1489 | genpd->suspended_count = 0; | 1500 | genpd->suspended_count = 0; |
| 1501 | genpd->max_off_time_ns = -1; | ||
| 1490 | genpd->domain.ops.runtime_suspend = pm_genpd_runtime_suspend; | 1502 | genpd->domain.ops.runtime_suspend = pm_genpd_runtime_suspend; |
| 1491 | genpd->domain.ops.runtime_resume = pm_genpd_runtime_resume; | 1503 | genpd->domain.ops.runtime_resume = pm_genpd_runtime_resume; |
| 1492 | genpd->domain.ops.runtime_idle = pm_generic_runtime_idle; | 1504 | genpd->domain.ops.runtime_idle = pm_generic_runtime_idle; |
diff --git a/drivers/base/power/domain_governor.c b/drivers/base/power/domain_governor.c index 97b21c10c496..da78540e9b40 100644 --- a/drivers/base/power/domain_governor.c +++ b/drivers/base/power/domain_governor.c | |||
| @@ -10,6 +10,7 @@ | |||
| 10 | #include <linux/kernel.h> | 10 | #include <linux/kernel.h> |
| 11 | #include <linux/pm_domain.h> | 11 | #include <linux/pm_domain.h> |
| 12 | #include <linux/pm_qos.h> | 12 | #include <linux/pm_qos.h> |
| 13 | #include <linux/hrtimer.h> | ||
| 13 | 14 | ||
| 14 | /** | 15 | /** |
| 15 | * default_stop_ok - Default PM domain governor routine for stopping devices. | 16 | * default_stop_ok - Default PM domain governor routine for stopping devices. |
| @@ -28,6 +29,115 @@ bool default_stop_ok(struct device *dev) | |||
| 28 | && td->break_even_ns < dev->power.max_time_suspended_ns; | 29 | && td->break_even_ns < dev->power.max_time_suspended_ns; |
| 29 | } | 30 | } |
| 30 | 31 | ||
| 32 | /** | ||
| 33 | * default_power_down_ok - Default generic PM domain power off governor routine. | ||
| 34 | * @pd: PM domain to check. | ||
| 35 | * | ||
| 36 | * This routine must be executed under the PM domain's lock. | ||
| 37 | */ | ||
| 38 | static bool default_power_down_ok(struct dev_pm_domain *pd) | ||
| 39 | { | ||
| 40 | struct generic_pm_domain *genpd = pd_to_genpd(pd); | ||
| 41 | struct gpd_link *link; | ||
| 42 | struct pm_domain_data *pdd; | ||
| 43 | s64 min_dev_off_time_ns; | ||
| 44 | s64 off_on_time_ns; | ||
| 45 | ktime_t time_now = ktime_get(); | ||
| 46 | |||
| 47 | off_on_time_ns = genpd->power_off_latency_ns + | ||
| 48 | genpd->power_on_latency_ns; | ||
| 49 | /* | ||
| 50 | * It doesn't make sense to remove power from the domain if saving | ||
| 51 | * the state of all devices in it and the power off/power on operations | ||
| 52 | * take too much time. | ||
| 53 | * | ||
| 54 | * All devices in this domain have been stopped already at this point. | ||
| 55 | */ | ||
| 56 | list_for_each_entry(pdd, &genpd->dev_list, list_node) { | ||
| 57 | if (pdd->dev->driver) | ||
| 58 | off_on_time_ns += | ||
| 59 | to_gpd_data(pdd)->td.save_state_latency_ns; | ||
| 60 | } | ||
| 61 | |||
| 62 | /* | ||
| 63 | * Check if subdomains can be off for enough time. | ||
| 64 | * | ||
| 65 | * All subdomains have been powered off already at this point. | ||
| 66 | */ | ||
| 67 | list_for_each_entry(link, &genpd->master_links, master_node) { | ||
| 68 | struct generic_pm_domain *sd = link->slave; | ||
| 69 | s64 sd_max_off_ns = sd->max_off_time_ns; | ||
| 70 | |||
| 71 | if (sd_max_off_ns < 0) | ||
| 72 | continue; | ||
| 73 | |||
| 74 | sd_max_off_ns -= ktime_to_ns(ktime_sub(time_now, | ||
| 75 | sd->power_off_time)); | ||
| 76 | /* | ||
| 77 | * Check if the subdomain is allowed to be off long enough for | ||
| 78 | * the current domain to turn off and on (that's how much time | ||
| 79 | * it will have to wait worst case). | ||
| 80 | */ | ||
| 81 | if (sd_max_off_ns <= off_on_time_ns) | ||
| 82 | return false; | ||
| 83 | } | ||
| 84 | |||
| 85 | /* | ||
| 86 | * Check if the devices in the domain can be off enough time. | ||
| 87 | */ | ||
| 88 | min_dev_off_time_ns = -1; | ||
| 89 | list_for_each_entry(pdd, &genpd->dev_list, list_node) { | ||
| 90 | struct gpd_timing_data *td; | ||
| 91 | struct device *dev = pdd->dev; | ||
| 92 | s64 dev_off_time_ns; | ||
| 93 | |||
| 94 | if (!dev->driver || dev->power.max_time_suspended_ns < 0) | ||
| 95 | continue; | ||
| 96 | |||
| 97 | td = &to_gpd_data(pdd)->td; | ||
| 98 | dev_off_time_ns = dev->power.max_time_suspended_ns - | ||
| 99 | (td->start_latency_ns + td->restore_state_latency_ns + | ||
| 100 | ktime_to_ns(ktime_sub(time_now, | ||
| 101 | dev->power.suspend_time))); | ||
| 102 | if (dev_off_time_ns <= off_on_time_ns) | ||
| 103 | return false; | ||
| 104 | |||
| 105 | if (min_dev_off_time_ns > dev_off_time_ns | ||
| 106 | || min_dev_off_time_ns < 0) | ||
| 107 | min_dev_off_time_ns = dev_off_time_ns; | ||
| 108 | } | ||
| 109 | |||
| 110 | if (min_dev_off_time_ns < 0) { | ||
| 111 | /* | ||
| 112 | * There are no latency constraints, so the domain can spend | ||
| 113 | * arbitrary time in the "off" state. | ||
| 114 | */ | ||
| 115 | genpd->max_off_time_ns = -1; | ||
| 116 | return true; | ||
| 117 | } | ||
| 118 | |||
| 119 | /* | ||
| 120 | * The difference between the computed minimum delta and the time needed | ||
| 121 | * to turn the domain on is the maximum theoretical time this domain can | ||
| 122 | * spend in the "off" state. | ||
| 123 | */ | ||
| 124 | min_dev_off_time_ns -= genpd->power_on_latency_ns; | ||
| 125 | |||
| 126 | /* | ||
| 127 | * If the difference between the computed minimum delta and the time | ||
| 128 | * needed to turn the domain off and back on on is smaller than the | ||
| 129 | * domain's power break even time, removing power from the domain is not | ||
| 130 | * worth it. | ||
| 131 | */ | ||
| 132 | if (genpd->break_even_ns > | ||
| 133 | min_dev_off_time_ns - genpd->power_off_latency_ns) | ||
| 134 | return false; | ||
| 135 | |||
| 136 | genpd->max_off_time_ns = min_dev_off_time_ns; | ||
| 137 | return true; | ||
| 138 | } | ||
| 139 | |||
| 31 | struct dev_power_governor simple_qos_governor = { | 140 | struct dev_power_governor simple_qos_governor = { |
| 32 | .stop_ok = default_stop_ok, | 141 | .stop_ok = default_stop_ok, |
| 142 | .power_down_ok = default_power_down_ok, | ||
| 33 | }; | 143 | }; |
diff --git a/include/linux/pm_domain.h b/include/linux/pm_domain.h index f6745c213a57..cc1a2450ff7b 100644 --- a/include/linux/pm_domain.h +++ b/include/linux/pm_domain.h | |||
| @@ -61,8 +61,13 @@ struct generic_pm_domain { | |||
| 61 | bool suspend_power_off; /* Power status before system suspend */ | 61 | bool suspend_power_off; /* Power status before system suspend */ |
| 62 | bool dev_irq_safe; /* Device callbacks are IRQ-safe */ | 62 | bool dev_irq_safe; /* Device callbacks are IRQ-safe */ |
| 63 | int (*power_off)(struct generic_pm_domain *domain); | 63 | int (*power_off)(struct generic_pm_domain *domain); |
| 64 | s64 power_off_latency_ns; | ||
| 64 | int (*power_on)(struct generic_pm_domain *domain); | 65 | int (*power_on)(struct generic_pm_domain *domain); |
| 66 | s64 power_on_latency_ns; | ||
| 65 | struct gpd_dev_ops dev_ops; | 67 | struct gpd_dev_ops dev_ops; |
| 68 | s64 break_even_ns; /* Power break even for the entire domain. */ | ||
| 69 | s64 max_off_time_ns; /* Maximum allowed "suspended" time. */ | ||
| 70 | ktime_t power_off_time; | ||
| 66 | }; | 71 | }; |
| 67 | 72 | ||
| 68 | static inline struct generic_pm_domain *pd_to_genpd(struct dev_pm_domain *pd) | 73 | static inline struct generic_pm_domain *pd_to_genpd(struct dev_pm_domain *pd) |
| @@ -80,6 +85,8 @@ struct gpd_link { | |||
| 80 | struct gpd_timing_data { | ||
