| From d3ade56cdfb6d8f10e32198ba9f998396189a885 Mon Sep 17 00:00:00 2001 |
| From: "Rafael J. Wysocki" <rjw@sisk.pl> |
| 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 <rjw@sisk.pl> |
| Tested-by: Simon Horman <horms@verge.net.au> |
| Acked-by: Paul Mundt <lethal@linux-sh.org> |
| Cc: stable@vger.kernel.org |
| (cherry picked from commit 1e78a0c7fc92aee076965d516cf54475c39e9894) |
| |
| Signed-off-by: Simon Horman <horms@verge.net.au> |
| --- |
| 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 84f4bee..b6ff6ec 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); |
| } |
| |
| /** |
| @@ -1320,6 +1325,26 @@ int pm_genpd_remove_device(struct generic_pm_domain *genpd, |
| } |
| |
| /** |
| + * 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. |
| * @subdomain: Subdomain to be added. |
| diff --git a/include/linux/pm_domain.h b/include/linux/pm_domain.h |
| index 5c2bbc2..1236d26 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) |
| { |
| -- |
| 1.7.10.1.362.g242cab3 |
| |