From cc85b20780562d404e18a47b9b55b4a5102ae53e Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Tue, 13 Mar 2012 22:39:31 +0100 Subject: PM / Domains: Fix handling of wakeup devices during system resume During system suspend pm_genpd_suspend_noirq() checks if the given device is in a wakeup path (i.e. it appears to be needed for one or more wakeup devices to work or is a wakeup device itself) and if it needs to be "active" for wakeup to work. If that is the case, the function returns 0 without incrementing the device domain's counter of suspended devices and without executing genpd_stop_dev() for the device. In consequence, the device is not stopped (e.g. its clock isn't disabled) and power is always supplied to its domain in the resulting system sleep state. However, pm_genpd_resume_noirq() doesn't repeat that check and it runs genpd_start_dev() and decrements the domain's counter of suspended devices even for the wakeup device that weren't stopped by pm_genpd_suspend_noirq(). As a result, the start callback may be run unnecessarily for them and their domains' counters of suspended devices may become negative. Both outcomes aren't desirable, so fix pm_genpd_resume_noirq() to look for wakeup devices that might not be stopped by during system suspend. Signed-off-by: Rafael J. Wysocki Tested-by: Simon Horman Cc: stable@vger.kernel.org --- drivers/base/power/domain.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'drivers/base') diff --git a/drivers/base/power/domain.c b/drivers/base/power/domain.c index d2c03239abc..e79228c597f 100644 --- a/drivers/base/power/domain.c +++ b/drivers/base/power/domain.c @@ -890,7 +890,8 @@ static int pm_genpd_resume_noirq(struct device *dev) if (IS_ERR(genpd)) return -EINVAL; - if (genpd->suspend_power_off) + if (genpd->suspend_power_off + || (dev->power.wakeup_path && genpd_dev_active_wakeup(genpd, dev))) return 0; /* -- cgit v1.2.3-18-g5258 From 65533bbf63b4f37723fdfedc73d0653958973323 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Tue, 13 Mar 2012 22:39:37 +0100 Subject: PM / Domains: Fix hibernation restore of devices, v2 During resume from hibernation pm_genpd_restore_noirq() should only power off domains whose suspend_power_off flags are set once and not every time it is called for a device in the given domain. Moreover, it shouldn't decrement genpd->suspended_count, because that field is not touched during device freezing and therefore it is always equal to 0 when pm_genpd_restore_noirq() runs for the first device in the given domain. This means pm_genpd_restore_noirq() may use genpd->suspended_count to determine whether or not it it has been called for the domain in question already in this cycle (it only needs to increment that field every time it runs for this purpose) and whether or not it should check if the domain needs to be powered off. For that to work, though, pm_genpd_prepare() has to clear genpd->suspended_count when it runs for the first device in the given domain (in which case that flag need not be cleared during domain initialization). Signed-off-by: Rafael J. Wysocki Cc: stable@vger.kernel.org --- drivers/base/power/domain.c | 31 +++++++++++++++++++++---------- 1 file changed, 21 insertions(+), 10 deletions(-) (limited to 'drivers/base') diff --git a/drivers/base/power/domain.c b/drivers/base/power/domain.c index e79228c597f..84f4beefa4f 100644 --- a/drivers/base/power/domain.c +++ b/drivers/base/power/domain.c @@ -764,8 +764,10 @@ static int pm_genpd_prepare(struct device *dev) genpd_acquire_lock(genpd); - if (genpd->prepared_count++ == 0) + if (genpd->prepared_count++ == 0) { + genpd->suspended_count = 0; genpd->suspend_power_off = genpd->status == GPD_STATE_POWER_OFF; + } genpd_release_lock(genpd); @@ -1097,20 +1099,30 @@ static int pm_genpd_restore_noirq(struct device *dev) * Since all of the "noirq" callbacks are executed sequentially, it is * guaranteed that this function will never run twice in parallel for * the same PM domain, so it is not necessary to use locking here. + * + * At this point suspended_count == 0 means we are being run for the + * first time for the given domain in the present cycle. */ - genpd->status = GPD_STATE_POWER_OFF; - if (genpd->suspend_power_off) { + if (genpd->suspended_count++ == 0) { /* - * The boot kernel might put the domain into the power on state, - * so make sure it really is powered off. + * The boot kernel might put the domain into arbitrary state, + * so make it appear as powered off to pm_genpd_poweron(), so + * that it tries to power it on in case it was really off. */ - if (genpd->power_off) - genpd->power_off(genpd); - return 0; + genpd->status = GPD_STATE_POWER_OFF; + if (genpd->suspend_power_off) { + /* + * If the domain was off before the hibernation, make + * sure it will be off going forward. + */ + if (genpd->power_off) + genpd->power_off(genpd); + + return 0; + } } pm_genpd_poweron(genpd); - genpd->suspended_count--; return genpd_start_dev(genpd, dev); } @@ -1649,7 +1661,6 @@ void pm_genpd_init(struct generic_pm_domain *genpd, genpd->poweroff_task = NULL; genpd->resume_count = 0; genpd->device_count = 0; - genpd->suspended_count = 0; genpd->max_off_time_ns = -1; genpd->domain.ops.runtime_suspend = pm_genpd_runtime_suspend; genpd->domain.ops.runtime_resume = pm_genpd_runtime_resume; -- cgit v1.2.3-18-g5258 From 1e78a0c7fc92aee076965d516cf54475c39e9894 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Tue, 13 Mar 2012 22:39:48 +0100 Subject: PM / Domains: Introduce "always on" device flag The TMU device on the Mackerel board belongs to the A4R power domain and loses power when the domain is turned off. Unfortunately, the TMU driver is not prepared to cope with such situations and crashes the system when that happens. To work around this problem introduce a new helper function, pm_genpd_dev_always_on(), allowing a device driver to mark its device as "always on" in case it belongs to a PM domain, which will make the generic PM domains core code avoid powering off the domain containing the device, both at run time and during system suspend. Signed-off-by: Rafael J. Wysocki Tested-by: Simon Horman Acked-by: Paul Mundt Cc: stable@vger.kernel.org --- drivers/base/power/domain.c | 37 +++++++++++++++++++++++++++++++------ 1 file changed, 31 insertions(+), 6 deletions(-) (limited to 'drivers/base') diff --git a/drivers/base/power/domain.c b/drivers/base/power/domain.c index 84f4beefa4f..b6ff6ecf519 100644 --- a/drivers/base/power/domain.c +++ b/drivers/base/power/domain.c @@ -366,7 +366,7 @@ static int pm_genpd_poweroff(struct generic_pm_domain *genpd) not_suspended = 0; list_for_each_entry(pdd, &genpd->dev_list, list_node) if (pdd->dev->driver && (!pm_runtime_suspended(pdd->dev) - || pdd->dev->power.irq_safe)) + || pdd->dev->power.irq_safe || to_gpd_data(pdd)->always_on)) not_suspended++; if (not_suspended > genpd->in_progress) @@ -503,6 +503,9 @@ static int pm_genpd_runtime_suspend(struct device *dev) might_sleep_if(!genpd->dev_irq_safe); + if (dev_gpd_data(dev)->always_on) + return -EBUSY; + stop_ok = genpd->gov ? genpd->gov->stop_ok : NULL; if (stop_ok && !stop_ok(dev)) return -EBUSY; @@ -859,7 +862,7 @@ static int pm_genpd_suspend_noirq(struct device *dev) if (IS_ERR(genpd)) return -EINVAL; - if (genpd->suspend_power_off + if (genpd->suspend_power_off || dev_gpd_data(dev)->always_on || (dev->power.wakeup_path && genpd_dev_active_wakeup(genpd, dev))) return 0; @@ -892,7 +895,7 @@ static int pm_genpd_resume_noirq(struct device *dev) if (IS_ERR(genpd)) return -EINVAL; - if (genpd->suspend_power_off + if (genpd->suspend_power_off || dev_gpd_data(dev)->always_on || (dev->power.wakeup_path && genpd_dev_active_wakeup(genpd, dev))) return 0; @@ -1012,7 +1015,8 @@ static int pm_genpd_freeze_noirq(struct device *dev) if (IS_ERR(genpd)) return -EINVAL; - return genpd->suspend_power_off ? 0 : genpd_stop_dev(genpd, dev); + return genpd->suspend_power_off || dev_gpd_data(dev)->always_on ? + 0 : genpd_stop_dev(genpd, dev); } /** @@ -1032,7 +1036,8 @@ static int pm_genpd_thaw_noirq(struct device *dev) if (IS_ERR(genpd)) return -EINVAL; - return genpd->suspend_power_off ? 0 : genpd_start_dev(genpd, dev); + return genpd->suspend_power_off || dev_gpd_data(dev)->always_on ? + 0 : genpd_start_dev(genpd, dev); } /** @@ -1124,7 +1129,7 @@ static int pm_genpd_restore_noirq(struct device *dev) pm_genpd_poweron(genpd); - return genpd_start_dev(genpd, dev); + return dev_gpd_data(dev)->always_on ? 0 : genpd_start_dev(genpd, dev); } /** @@ -1319,6 +1324,26 @@ int pm_genpd_remove_device(struct generic_pm_domain *genpd, return ret; } +/** + * pm_genpd_dev_always_on - Set/unset the "always on" flag for a given device. + * @dev: Device to set/unset the flag for. + * @val: The new value of the device's "always on" flag. + */ +void pm_genpd_dev_always_on(struct device *dev, bool val) +{ + struct pm_subsys_data *psd; + unsigned long flags; + + spin_lock_irqsave(&dev->power.lock, flags); + + psd = dev_to_psd(dev); + if (psd && psd->domain_data) + to_gpd_data(psd->domain_data)->always_on = val; + + spin_unlock_irqrestore(&dev->power.lock, flags); +} +EXPORT_SYMBOL_GPL(pm_genpd_dev_always_on); + /** * pm_genpd_add_subdomain - Add a subdomain to an I/O PM domain. * @genpd: Master PM domain to add the subdomain to. -- cgit v1.2.3-18-g5258