From cc85b20780562d404e18a47b9b55b4a5102ae53e Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Tue, 13 Mar 2012 22:39:31 +0100 Subject: [PATCH 1/6] 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(-) diff --git a/drivers/base/power/domain.c b/drivers/base/power/domain.c index d2c03239abcf..e79228c597f1 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; /* From 65533bbf63b4f37723fdfedc73d0653958973323 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Tue, 13 Mar 2012 22:39:37 +0100 Subject: [PATCH 2/6] 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(-) diff --git a/drivers/base/power/domain.c b/drivers/base/power/domain.c index e79228c597f1..84f4beefa4f8 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; From 1e78a0c7fc92aee076965d516cf54475c39e9894 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Tue, 13 Mar 2012 22:39:48 +0100 Subject: [PATCH 3/6] 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 +++++++++++++++++++++++++++++++------ include/linux/pm_domain.h | 3 +++ 2 files changed, 34 insertions(+), 6 deletions(-) diff --git a/drivers/base/power/domain.c b/drivers/base/power/domain.c index 84f4beefa4f8..b6ff6ecf519d 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. diff --git a/include/linux/pm_domain.h b/include/linux/pm_domain.h index 5c2bbc248c11..1236d262b3e8 100644 --- a/include/linux/pm_domain.h +++ b/include/linux/pm_domain.h @@ -99,6 +99,7 @@ struct generic_pm_domain_data { struct gpd_dev_ops ops; struct gpd_timing_data td; bool need_restore; + bool always_on; }; #ifdef CONFIG_PM_GENERIC_DOMAINS @@ -137,6 +138,7 @@ static inline int pm_genpd_of_add_device(struct device_node *genpd_node, extern int pm_genpd_remove_device(struct generic_pm_domain *genpd, struct device *dev); +extern void pm_genpd_dev_always_on(struct device *dev, bool val); extern int pm_genpd_add_subdomain(struct generic_pm_domain *genpd, struct generic_pm_domain *new_subdomain); extern int pm_genpd_remove_subdomain(struct generic_pm_domain *genpd, @@ -179,6 +181,7 @@ static inline int pm_genpd_remove_device(struct generic_pm_domain *genpd, { return -ENOSYS; } +static inline void pm_genpd_dev_always_on(struct device *dev, bool val) {} static inline int pm_genpd_add_subdomain(struct generic_pm_domain *genpd, struct generic_pm_domain *new_sd) { From 2ee619f9487c2acc1efdf2c78e68e2bd51b635fa Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Tue, 13 Mar 2012 22:40:00 +0100 Subject: [PATCH 4/6] PM / shmobile: Make TMU driver use pm_genpd_dev_always_on() Make the TMU clocksource driver mark its device as "always on" using pm_genpd_dev_always_on() to protect it from surprise power removals and make sh7372_add_standard_devices() add TMU devices on sh7372 to the A4R power domain so that their "always on" flags are taken into account as appropriate. Signed-off-by: Rafael J. Wysocki Tested-by: Simon Horman Acked-by: Paul Mundt Cc: stable@vger.kernel.org --- arch/arm/mach-shmobile/setup-sh7372.c | 2 ++ drivers/clocksource/sh_tmu.c | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/arch/arm/mach-shmobile/setup-sh7372.c b/arch/arm/mach-shmobile/setup-sh7372.c index 6fcf304d3cdf..9f2d7f2b5374 100644 --- a/arch/arm/mach-shmobile/setup-sh7372.c +++ b/arch/arm/mach-shmobile/setup-sh7372.c @@ -1041,6 +1041,8 @@ void __init sh7372_add_standard_devices(void) sh7372_add_device_to_domain(&sh7372_a4r, &veu2_device); sh7372_add_device_to_domain(&sh7372_a4r, &veu3_device); sh7372_add_device_to_domain(&sh7372_a4r, &jpu_device); + sh7372_add_device_to_domain(&sh7372_a4r, &tmu00_device); + sh7372_add_device_to_domain(&sh7372_a4r, &tmu01_device); } void __init sh7372_add_early_devices(void) diff --git a/drivers/clocksource/sh_tmu.c b/drivers/clocksource/sh_tmu.c index 079e96ad44e8..97f54b634be4 100644 --- a/drivers/clocksource/sh_tmu.c +++ b/drivers/clocksource/sh_tmu.c @@ -32,6 +32,7 @@ #include #include #include +#include struct sh_tmu_priv { void __iomem *mapbase; @@ -410,6 +411,9 @@ static int __devinit sh_tmu_probe(struct platform_device *pdev) struct sh_tmu_priv *p = platform_get_drvdata(pdev); int ret; + if (!is_early_platform_device(pdev)) + pm_genpd_dev_always_on(&pdev->dev, true); + if (p) { dev_info(&pdev->dev, "kept as earlytimer\n"); return 0; From 615a445f7f8a077c145e737864ae59a4d8717882 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Tue, 13 Mar 2012 22:40:06 +0100 Subject: [PATCH 5/6] PM / shmobile: Make CMT driver use pm_genpd_dev_always_on() Make the CMT clocksource driver mark its device as "always on" using pm_genpd_dev_always_on() to protect it from surprise power removals. Signed-off-by: Rafael J. Wysocki Tested-by: Simon Horman Acked-by: Paul Mundt Cc: stable@vger.kernel.org --- drivers/clocksource/sh_cmt.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/clocksource/sh_cmt.c b/drivers/clocksource/sh_cmt.c index ca09bc421ddb..32fe9ef5cc5c 100644 --- a/drivers/clocksource/sh_cmt.c +++ b/drivers/clocksource/sh_cmt.c @@ -32,6 +32,7 @@ #include #include #include +#include struct sh_cmt_priv { void __iomem *mapbase; @@ -689,6 +690,9 @@ static int __devinit sh_cmt_probe(struct platform_device *pdev) struct sh_cmt_priv *p = platform_get_drvdata(pdev); int ret; + if (!is_early_platform_device(pdev)) + pm_genpd_dev_always_on(&pdev->dev, true); + if (p) { dev_info(&pdev->dev, "kept as earlytimer\n"); return 0; From 57d13370cfaf6017c68981e66ff5b3bf20a2705c Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Tue, 13 Mar 2012 22:40:14 +0100 Subject: [PATCH 6/6] PM / shmobile: Make MTU2 driver use pm_genpd_dev_always_on() Make the MTU2 clocksource driver mark its device as "always on" using pm_genpd_dev_always_on() to protect it from surprise power removals. Signed-off-by: Rafael J. Wysocki Tested-by: Simon Horman Acked-by: Paul Mundt Cc: stable@vger.kernel.org --- drivers/clocksource/sh_mtu2.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/clocksource/sh_mtu2.c b/drivers/clocksource/sh_mtu2.c index db8d5955bad4..a2172f690418 100644 --- a/drivers/clocksource/sh_mtu2.c +++ b/drivers/clocksource/sh_mtu2.c @@ -31,6 +31,7 @@ #include #include #include +#include struct sh_mtu2_priv { void __iomem *mapbase; @@ -306,6 +307,9 @@ static int __devinit sh_mtu2_probe(struct platform_device *pdev) struct sh_mtu2_priv *p = platform_get_drvdata(pdev); int ret; + if (!is_early_platform_device(pdev)) + pm_genpd_dev_always_on(&pdev->dev, true); + if (p) { dev_info(&pdev->dev, "kept as earlytimer\n"); return 0;