From f0c28b0075cad861f4b93c526c6446169d136466 Mon Sep 17 00:00:00 2001 From: Axel Lin Date: Sat, 14 Jan 2012 16:56:43 +0800 Subject: devfreq: exynos4_bus: Use dev_get_drvdata at appropriate places Signed-off-by: Axel Lin Signed-off-by: MyungJoo Ham --- drivers/devfreq/exynos4_bus.c | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) (limited to 'drivers') diff --git a/drivers/devfreq/exynos4_bus.c b/drivers/devfreq/exynos4_bus.c index 6460577d6701..489ccfa93a96 100644 --- a/drivers/devfreq/exynos4_bus.c +++ b/drivers/devfreq/exynos4_bus.c @@ -622,9 +622,7 @@ static int exynos4_bus_setvolt(struct busfreq_data *data, struct opp *opp, static int exynos4_bus_target(struct device *dev, unsigned long *_freq) { int err = 0; - struct platform_device *pdev = container_of(dev, struct platform_device, - dev); - struct busfreq_data *data = platform_get_drvdata(pdev); + struct busfreq_data *data = dev_get_drvdata(dev); struct opp *opp = devfreq_recommended_opp(dev, _freq); unsigned long old_freq = opp_get_freq(data->curr_opp); unsigned long freq = opp_get_freq(opp); @@ -689,9 +687,7 @@ static int exynos4_get_busier_dmc(struct busfreq_data *data) static int exynos4_bus_get_dev_status(struct device *dev, struct devfreq_dev_status *stat) { - struct platform_device *pdev = container_of(dev, struct platform_device, - dev); - struct busfreq_data *data = platform_get_drvdata(pdev); + struct busfreq_data *data = dev_get_drvdata(dev); int busier_dmc; int cycles_x2 = 2; /* 2 x cycles */ void __iomem *addr; @@ -739,9 +735,7 @@ static int exynos4_bus_get_dev_status(struct device *dev, static void exynos4_bus_exit(struct device *dev) { - struct platform_device *pdev = container_of(dev, struct platform_device, - dev); - struct busfreq_data *data = platform_get_drvdata(pdev); + struct busfreq_data *data = dev_get_drvdata(dev); devfreq_unregister_opp_notifier(dev, data->devfreq); } @@ -1087,9 +1081,7 @@ static __devexit int exynos4_busfreq_remove(struct platform_device *pdev) static int exynos4_busfreq_resume(struct device *dev) { - struct platform_device *pdev = container_of(dev, struct platform_device, - dev); - struct busfreq_data *data = platform_get_drvdata(pdev); + struct busfreq_data *data = dev_get_drvdata(dev); busfreq_mon_reset(data); return 0; -- cgit v1.2.3 From e0d44e8ab06885a5bb980f3d6d4cf64ad430d406 Mon Sep 17 00:00:00 2001 From: Axel Lin Date: Mon, 16 Jan 2012 14:53:08 +0800 Subject: devfreq: Remove MODULE_ALIAS for exynos4 busfreq driver This driver can only be built-in, it does not make sense to add modalias for it (in addition to being incorrect, the platform modalias needs to be prefixed with "platform:"). Signed-off-by: Axel Lin Signed-off-by: MyungJoo Ham --- drivers/devfreq/exynos4_bus.c | 1 - 1 file changed, 1 deletion(-) (limited to 'drivers') diff --git a/drivers/devfreq/exynos4_bus.c b/drivers/devfreq/exynos4_bus.c index 489ccfa93a96..590d6865e388 100644 --- a/drivers/devfreq/exynos4_bus.c +++ b/drivers/devfreq/exynos4_bus.c @@ -1124,4 +1124,3 @@ module_exit(exynos4_busfreq_exit); MODULE_LICENSE("GPL"); MODULE_DESCRIPTION("EXYNOS4 busfreq driver with devfreq framework"); MODULE_AUTHOR("MyungJoo Ham "); -MODULE_ALIAS("exynos4-busfreq"); -- cgit v1.2.3 From 6530b9dea1b7f33eaf79ba625e3a99f2455f3eb1 Mon Sep 17 00:00:00 2001 From: MyungJoo Ham Date: Fri, 9 Dec 2011 16:42:19 +0900 Subject: PM / devfreq: add min/max_freq limit requested by users. The frequency requested to devfreq device driver from devfreq governors is restricted by min_freq and max_freq input. Signed-off-by: MyungJoo Ham Signed-off-by: Kyungmin Park --- drivers/devfreq/devfreq.c | 70 +++++++++++++++++++++++++++++++ drivers/devfreq/governor_performance.c | 5 ++- drivers/devfreq/governor_powersave.c | 2 +- drivers/devfreq/governor_simpleondemand.c | 12 ++++-- drivers/devfreq/governor_userspace.c | 15 +++++-- 5 files changed, 96 insertions(+), 8 deletions(-) (limited to 'drivers') diff --git a/drivers/devfreq/devfreq.c b/drivers/devfreq/devfreq.c index c189b82f5ece..a129a7b6bfd1 100644 --- a/drivers/devfreq/devfreq.c +++ b/drivers/devfreq/devfreq.c @@ -501,12 +501,82 @@ static ssize_t show_central_polling(struct device *dev, !to_devfreq(dev)->governor->no_central_polling); } +static ssize_t store_min_freq(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct devfreq *df = to_devfreq(dev); + unsigned long value; + int ret; + unsigned long max; + + ret = sscanf(buf, "%lu", &value); + if (ret != 1) + goto out; + + mutex_lock(&df->lock); + max = df->max_freq; + if (value && max && value > max) { + ret = -EINVAL; + goto unlock; + } + + df->min_freq = value; + update_devfreq(df); + ret = count; +unlock: + mutex_unlock(&df->lock); +out: + return ret; +} + +static ssize_t show_min_freq(struct device *dev, struct device_attribute *attr, + char *buf) +{ + return sprintf(buf, "%lu\n", to_devfreq(dev)->min_freq); +} + +static ssize_t store_max_freq(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct devfreq *df = to_devfreq(dev); + unsigned long value; + int ret; + unsigned long min; + + ret = sscanf(buf, "%lu", &value); + if (ret != 1) + goto out; + + mutex_lock(&df->lock); + min = df->min_freq; + if (value && min && value < min) { + ret = -EINVAL; + goto unlock; + } + + df->max_freq = value; + update_devfreq(df); + ret = count; +unlock: + mutex_unlock(&df->lock); +out: + return ret; +} + +static ssize_t show_max_freq(struct device *dev, struct device_attribute *attr, + char *buf) +{ + return sprintf(buf, "%lu\n", to_devfreq(dev)->max_freq); +} + static struct device_attribute devfreq_attrs[] = { __ATTR(governor, S_IRUGO, show_governor, NULL), __ATTR(cur_freq, S_IRUGO, show_freq, NULL), __ATTR(central_polling, S_IRUGO, show_central_polling, NULL), __ATTR(polling_interval, S_IRUGO | S_IWUSR, show_polling_interval, store_polling_interval), + __ATTR(min_freq, S_IRUGO | S_IWUSR, show_min_freq, store_min_freq), + __ATTR(max_freq, S_IRUGO | S_IWUSR, show_max_freq, store_max_freq), { }, }; diff --git a/drivers/devfreq/governor_performance.c b/drivers/devfreq/governor_performance.c index c0596b291761..574a06b1b1de 100644 --- a/drivers/devfreq/governor_performance.c +++ b/drivers/devfreq/governor_performance.c @@ -18,7 +18,10 @@ static int devfreq_performance_func(struct devfreq *df, * target callback should be able to get floor value as * said in devfreq.h */ - *freq = UINT_MAX; + if (!df->max_freq) + *freq = UINT_MAX; + else + *freq = df->max_freq; return 0; } diff --git a/drivers/devfreq/governor_powersave.c b/drivers/devfreq/governor_powersave.c index 2483a85a266f..d742d4a82d6a 100644 --- a/drivers/devfreq/governor_powersave.c +++ b/drivers/devfreq/governor_powersave.c @@ -18,7 +18,7 @@ static int devfreq_powersave_func(struct devfreq *df, * target callback should be able to get ceiling value as * said in devfreq.h */ - *freq = 0; + *freq = df->min_freq; return 0; } diff --git a/drivers/devfreq/governor_simpleondemand.c b/drivers/devfreq/governor_simpleondemand.c index efad8dcf9028..a2e3eae79011 100644 --- a/drivers/devfreq/governor_simpleondemand.c +++ b/drivers/devfreq/governor_simpleondemand.c @@ -25,6 +25,7 @@ static int devfreq_simple_ondemand_func(struct devfreq *df, unsigned int dfso_upthreshold = DFSO_UPTHRESHOLD; unsigned int dfso_downdifferential = DFSO_DOWNDIFFERENCTIAL; struct devfreq_simple_ondemand_data *data = df->data; + unsigned long max = (df->max_freq) ? df->max_freq : UINT_MAX; if (err) return err; @@ -41,7 +42,7 @@ static int devfreq_simple_ondemand_func(struct devfreq *df, /* Assume MAX if it is going to be divided by zero */ if (stat.total_time == 0) { - *freq = UINT_MAX; + *freq = max; return 0; } @@ -54,13 +55,13 @@ static int devfreq_simple_ondemand_func(struct devfreq *df, /* Set MAX if it's busy enough */ if (stat.busy_time * 100 > stat.total_time * dfso_upthreshold) { - *freq = UINT_MAX; + *freq = max; return 0; } /* Set MAX if we do not know the initial frequency */ if (stat.current_frequency == 0) { - *freq = UINT_MAX; + *freq = max; return 0; } @@ -79,6 +80,11 @@ static int devfreq_simple_ondemand_func(struct devfreq *df, b = div_u64(b, (dfso_upthreshold - dfso_downdifferential / 2)); *freq = (unsigned long) b; + if (df->min_freq && *freq < df->min_freq) + *freq = df->min_freq; + if (df->max_freq && *freq > df->max_freq) + *freq = df->max_freq; + return 0; } diff --git a/drivers/devfreq/governor_userspace.c b/drivers/devfreq/governor_userspace.c index 4f8b563da782..0681246fc89d 100644 --- a/drivers/devfreq/governor_userspace.c +++ b/drivers/devfreq/governor_userspace.c @@ -25,10 +25,19 @@ static int devfreq_userspace_func(struct devfreq *df, unsigned long *freq) { struct userspace_data *data = df->data; - if (!data->valid) + if (data->valid) { + unsigned long adjusted_freq = data->user_frequency; + + if (df->max_freq && adjusted_freq > df->max_freq) + adjusted_freq = df->max_freq; + + if (df->min_freq && adjusted_freq < df->min_freq) + adjusted_freq = df->min_freq; + + *freq = adjusted_freq; + } else { *freq = df->previous_freq; /* No user freq specified yet */ - else - *freq = data->user_frequency; + } return 0; } -- cgit v1.2.3 From c8aa130b74cc5b112cb2b119d3b477abaaf6e5b2 Mon Sep 17 00:00:00 2001 From: Thomas Abraham Date: Fri, 27 Jan 2012 15:22:07 +0900 Subject: PM / Domains: Add OF support A device node pointer is added to generic pm domain structure to associate the domain with a node in the device tree. The platform code parses the device tree to find available nodes representing the generic power domain, instantiates the available domains and initializes them by calling pm_genpd_init(). Nodes representing the devices include a phandle of the power domain to which it belongs. As these devices get instantiated, the driver code checkes for availability of a power domain phandle, converts the phandle to a device node and uses the new pm_genpd_of_add_device() api to associate the device with a power domain. pm_genpd_of_add_device() runs through its list of registered power domains and matches the OF node of the domain with the one specified as the parameter. If a match is found, the device is associated with the matched domain. Cc: Rob Herring Cc: Grant Likely Signed-off-by: Thomas Abraham Acked-by: Rafael J. Wysocki Signed-off-by: Kukjin Kim --- drivers/base/power/domain.c | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) (limited to 'drivers') diff --git a/drivers/base/power/domain.c b/drivers/base/power/domain.c index 978bbf7ac6af..939109b75c9b 100644 --- a/drivers/base/power/domain.c +++ b/drivers/base/power/domain.c @@ -1170,6 +1170,38 @@ int __pm_genpd_add_device(struct generic_pm_domain *genpd, struct device *dev, return ret; } +/** + * __pm_genpd_of_add_device - Add a device to an I/O PM domain. + * @genpd_node: Device tree node pointer representing a PM domain to which the + * the device is added to. + * @dev: Device to be added. + * @td: Set of PM QoS timing parameters to attach to the device. + */ +int __pm_genpd_of_add_device(struct device_node *genpd_node, struct device *dev, + struct gpd_timing_data *td) +{ + struct generic_pm_domain *genpd = NULL, *gpd; + + dev_dbg(dev, "%s()\n", __func__); + + if (IS_ERR_OR_NULL(genpd_node) || IS_ERR_OR_NULL(dev)) + return -EINVAL; + + mutex_lock(&gpd_list_lock); + list_for_each_entry(gpd, &gpd_list, gpd_list_node) { + if (gpd->of_node == genpd_node) { + genpd = gpd; + break; + } + } + mutex_unlock(&gpd_list_lock); + + if (!genpd) + return -EINVAL; + + return __pm_genpd_add_device(genpd, dev, td); +} + /** * pm_genpd_remove_device - Remove a device from an I/O PM domain. * @genpd: PM domain to remove the device from. -- cgit v1.2.3 From cf579dfb82550e34de7ccf3ef090d8b834ccd3a9 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Sun, 29 Jan 2012 20:38:29 +0100 Subject: PM / Sleep: Introduce "late suspend" and "early resume" of devices The current device suspend/resume phases during system-wide power transitions appear to be insufficient for some platforms that want to use the same callback routines for saving device states and related operations during runtime suspend/resume as well as during system suspend/resume. In principle, they could point their .suspend_noirq() and .resume_noirq() to the same callback routines as their .runtime_suspend() and .runtime_resume(), respectively, but at least some of them require device interrupts to be enabled while the code in those routines is running. It also makes sense to have device suspend-resume callbacks that will be executed with runtime PM disabled and with device interrupts enabled in case someone needs to run some special code in that context during system-wide power transitions. Apart from this, .suspend_noirq() and .resume_noirq() were introduced as a workaround for drivers using shared interrupts and failing to prevent their interrupt handlers from accessing suspended hardware. It appears to be better not to use them for other porposes, or we may have to deal with some serious confusion (which seems to be happening already). For the above reasons, introduce new device suspend/resume phases, "late suspend" and "early resume" (and analogously for hibernation) whose callback will be executed with runtime PM disabled and with device interrupts enabled and whose callback pointers generally may point to runtime suspend/resume routines. Signed-off-by: Rafael J. Wysocki Reviewed-by: Mark Brown Reviewed-by: Kevin Hilman --- drivers/base/power/main.c | 247 +++++++++++++++++++++++++++++++++++++++++----- drivers/xen/manage.c | 6 +- 2 files changed, 228 insertions(+), 25 deletions(-) (limited to 'drivers') diff --git a/drivers/base/power/main.c b/drivers/base/power/main.c index e2cc3d2e0ecc..b462c0e341cb 100644 --- a/drivers/base/power/main.c +++ b/drivers/base/power/main.c @@ -47,6 +47,7 @@ typedef int (*pm_callback_t)(struct device *); LIST_HEAD(dpm_list); LIST_HEAD(dpm_prepared_list); LIST_HEAD(dpm_suspended_list); +LIST_HEAD(dpm_late_early_list); LIST_HEAD(dpm_noirq_list); struct suspend_stats suspend_stats; @@ -245,6 +246,40 @@ static pm_callback_t pm_op(const struct dev_pm_ops *ops, pm_message_t state) return NULL; } +/** + * pm_late_early_op - Return the PM operation appropriate for given PM event. + * @ops: PM operations to choose from. + * @state: PM transition of the system being carried out. + * + * Runtime PM is disabled for @dev while this function is being executed. + */ +static pm_callback_t pm_late_early_op(const struct dev_pm_ops *ops, + pm_message_t state) +{ + switch (state.event) { +#ifdef CONFIG_SUSPEND + case PM_EVENT_SUSPEND: + return ops->suspend_late; + case PM_EVENT_RESUME: + return ops->resume_early; +#endif /* CONFIG_SUSPEND */ +#ifdef CONFIG_HIBERNATE_CALLBACKS + case PM_EVENT_FREEZE: + case PM_EVENT_QUIESCE: + return ops->freeze_late; + case PM_EVENT_HIBERNATE: + return ops->poweroff_late; + case PM_EVENT_THAW: + case PM_EVENT_RECOVER: + return ops->thaw_early; + case PM_EVENT_RESTORE: + return ops->restore_early; +#endif /* CONFIG_HIBERNATE_CALLBACKS */ + } + + return NULL; +} + /** * pm_noirq_op - Return the PM operation appropriate for given PM event. * @ops: PM operations to choose from. @@ -374,21 +409,21 @@ static int device_resume_noirq(struct device *dev, pm_message_t state) TRACE_RESUME(0); if (dev->pm_domain) { - info = "EARLY power domain "; + info = "noirq power domain "; callback = pm_noirq_op(&dev->pm_domain->ops, state); } else if (dev->type && dev->type->pm) { - info = "EARLY type "; + info = "noirq type "; callback = pm_noirq_op(dev->type->pm, state); } else if (dev->class && dev->class->pm) { - info = "EARLY class "; + info = "noirq class "; callback = pm_noirq_op(dev->class->pm, state); } else if (dev->bus && dev->bus->pm) { - info = "EARLY bus "; + info = "noirq bus "; callback = pm_noirq_op(dev->bus->pm, state); } if (!callback && dev->driver && dev->driver->pm) { - info = "EARLY driver "; + info = "noirq driver "; callback = pm_noirq_op(dev->driver->pm, state); } @@ -399,13 +434,13 @@ static int device_resume_noirq(struct device *dev, pm_message_t state) } /** - * dpm_resume_noirq - Execute "early resume" callbacks for non-sysdev devices. + * dpm_resume_noirq - Execute "noirq resume" callbacks for all devices. * @state: PM transition of the system being carried out. * - * Call the "noirq" resume handlers for all devices marked as DPM_OFF_IRQ and + * Call the "noirq" resume handlers for all devices in dpm_noirq_list and * enable device drivers to receive interrupts. */ -void dpm_resume_noirq(pm_message_t state) +static void dpm_resume_noirq(pm_message_t state) { ktime_t starttime = ktime_get(); @@ -415,7 +450,7 @@ void dpm_resume_noirq(pm_message_t state) int error; get_device(dev); - list_move_tail(&dev->power.entry, &dpm_suspended_list); + list_move_tail(&dev->power.entry, &dpm_late_early_list); mutex_unlock(&dpm_list_mtx); error = device_resume_noirq(dev, state); @@ -423,6 +458,80 @@ void dpm_resume_noirq(pm_message_t state) suspend_stats.failed_resume_noirq++; dpm_save_failed_step(SUSPEND_RESUME_NOIRQ); dpm_save_failed_dev(dev_name(dev)); + pm_dev_err(dev, state, " noirq", error); + } + + mutex_lock(&dpm_list_mtx); + put_device(dev); + } + mutex_unlock(&dpm_list_mtx); + dpm_show_time(starttime, state, "noirq"); + resume_device_irqs(); +} + +/** + * device_resume_early - Execute an "early resume" callback for given device. + * @dev: Device to handle. + * @state: PM transition of the system being carried out. + * + * Runtime PM is disabled for @dev while this function is being executed. + */ +static int device_resume_early(struct device *dev, pm_message_t state) +{ + pm_callback_t callback = NULL; + char *info = NULL; + int error = 0; + + TRACE_DEVICE(dev); + TRACE_RESUME(0); + + if (dev->pm_domain) { + info = "early power domain "; + callback = pm_late_early_op(&dev->pm_domain->ops, state); + } else if (dev->type && dev->type->pm) { + info = "early type "; + callback = pm_late_early_op(dev->type->pm, state); + } else if (dev->class && dev->class->pm) { + info = "early class "; + callback = pm_late_early_op(dev->class->pm, state); + } else if (dev->bus && dev->bus->pm) { + info = "early bus "; + callback = pm_late_early_op(dev->bus->pm, state); + } + + if (!callback && dev->driver && dev->driver->pm) { + info = "early driver "; + callback = pm_late_early_op(dev->driver->pm, state); + } + + error = dpm_run_callback(callback, dev, state, info); + + TRACE_RESUME(error); + return error; +} + +/** + * dpm_resume_early - Execute "early resume" callbacks for all devices. + * @state: PM transition of the system being carried out. + */ +static void dpm_resume_early(pm_message_t state) +{ + ktime_t starttime = ktime_get(); + + mutex_lock(&dpm_list_mtx); + while (!list_empty(&dpm_late_early_list)) { + struct device *dev = to_device(dpm_late_early_list.next); + int error; + + get_device(dev); + list_move_tail(&dev->power.entry, &dpm_suspended_list); + mutex_unlock(&dpm_list_mtx); + + error = device_resume_early(dev, state); + if (error) { + suspend_stats.failed_resume_early++; + dpm_save_failed_step(SUSPEND_RESUME_EARLY); + dpm_save_failed_dev(dev_name(dev)); pm_dev_err(dev, state, " early", error); } @@ -431,9 +540,18 @@ void dpm_resume_noirq(pm_message_t state) } mutex_unlock(&dpm_list_mtx); dpm_show_time(starttime, state, "early"); - resume_device_irqs(); } -EXPORT_SYMBOL_GPL(dpm_resume_noirq); + +/** + * dpm_resume_start - Execute "noirq" and "early" device callbacks. + * @state: PM transition of the system being carried out. + */ +void dpm_resume_start(pm_message_t state) +{ + dpm_resume_noirq(state); + dpm_resume_early(state); +} +EXPORT_SYMBOL_GPL(dpm_resume_start); /** * device_resume - Execute "resume" callbacks for given device. @@ -716,21 +834,21 @@ static int device_suspend_noirq(struct device *dev, pm_message_t state) char *info = NULL; if (dev->pm_domain) { - info = "LATE power domain "; + info = "noirq power domain "; callback = pm_noirq_op(&dev->pm_domain->ops, state); } else if (dev->type && dev->type->pm) { - info = "LATE type "; + info = "noirq type "; callback = pm_noirq_op(dev->type->pm, state); } else if (dev->class && dev->class->pm) { - info = "LATE class "; + info = "noirq class "; callback = pm_noirq_op(dev->class->pm, state); } else if (dev->bus && dev->bus->pm) { - info = "LATE bus "; + info = "noirq bus "; callback = pm_noirq_op(dev->bus->pm, state); } if (!callback && dev->driver && dev->driver->pm) { - info = "LATE driver "; + info = "noirq driver "; callback = pm_noirq_op(dev->driver->pm, state); } @@ -738,21 +856,21 @@ static int device_suspend_noirq(struct device *dev, pm_message_t state) } /** - * dpm_suspend_noirq - Execute "late suspend" callbacks for non-sysdev devices. + * dpm_suspend_noirq - Execute "noirq suspend" callbacks for all devices. * @state: PM transition of the system being carried out. * * Prevent device drivers from receiving interrupts and call the "noirq" suspend * handlers for all non-sysdev devices. */ -int dpm_suspend_noirq(pm_message_t state) +static int dpm_suspend_noirq(pm_message_t state) { ktime_t starttime = ktime_get(); int error = 0; suspend_device_irqs(); mutex_lock(&dpm_list_mtx); - while (!list_empty(&dpm_suspended_list)) { - struct device *dev = to_device(dpm_suspended_list.prev); + while (!list_empty(&dpm_late_early_list)) { + struct device *dev = to_device(dpm_late_early_list.prev); get_device(dev); mutex_unlock(&dpm_list_mtx); @@ -761,7 +879,7 @@ int dpm_suspend_noirq(pm_message_t state) mutex_lock(&dpm_list_mtx); if (error) { - pm_dev_err(dev, state, " late", error); + pm_dev_err(dev, state, " noirq", error); suspend_stats.failed_suspend_noirq++; dpm_save_failed_step(SUSPEND_SUSPEND_NOIRQ); dpm_save_failed_dev(dev_name(dev)); @@ -775,11 +893,96 @@ int dpm_suspend_noirq(pm_message_t state) mutex_unlock(&dpm_list_mtx); if (error) dpm_resume_noirq(resume_event(state)); + else + dpm_show_time(starttime, state, "noirq"); + return error; +} + +/** + * device_suspend_late - Execute a "late suspend" callback for given device. + * @dev: Device to handle. + * @state: PM transition of the system being carried out. + * + * Runtime PM is disabled for @dev while this function is being executed. + */ +static int device_suspend_late(struct device *dev, pm_message_t state) +{ + pm_callback_t callback = NULL; + char *info = NULL; + + if (dev->pm_domain) { + info = "late power domain "; + callback = pm_late_early_op(&dev->pm_domain->ops, state); + } else if (dev->type && dev->type->pm) { + info = "late type "; + callback = pm_late_early_op(dev->type->pm, state); + } else if (dev->class && dev->class->pm) { + info = "late class "; + callback = pm_late_early_op(dev->class->pm, state); + } else if (dev->bus && dev->bus->pm) { + info = "late bus "; + callback = pm_late_early_op(dev->bus->pm, state); + } + + if (!callback && dev->driver && dev->driver->pm) { + info = "late driver "; + callback = pm_late_early_op(dev->driver->pm, state); + } + + return dpm_run_callback(callback, dev, state, info); +} + +/** + * dpm_suspend_late - Execute "late suspend" callbacks for all devices. + * @state: PM transition of the system being carried out. + */ +static int dpm_suspend_late(pm_message_t state) +{ + ktime_t starttime = ktime_get(); + int error = 0; + + mutex_lock(&dpm_list_mtx); + while (!list_empty(&dpm_suspended_list)) { + struct device *dev = to_device(dpm_suspended_list.prev); + + get_device(dev); + mutex_unlock(&dpm_list_mtx); + + error = device_suspend_late(dev, state); + + mutex_lock(&dpm_list_mtx); + if (error) { + pm_dev_err(dev, state, " late", error); + suspend_stats.failed_suspend_late++; + dpm_save_failed_step(SUSPEND_SUSPEND_LATE); + dpm_save_failed_dev(dev_name(dev)); + put_device(dev); + break; + } + if (!list_empty(&dev->power.entry)) + list_move(&dev->power.entry, &dpm_late_early_list); + put_device(dev); + } + mutex_unlock(&dpm_list_mtx); + if (error) + dpm_resume_early(resume_event(state)); else dpm_show_time(starttime, state, "late"); + return error; } -EXPORT_SYMBOL_GPL(dpm_suspend_noirq); + +/** + * dpm_suspend_end - Execute "late" and "noirq" device suspend callbacks. + * @state: PM transition of the system being carried out. + */ +int dpm_suspend_end(pm_message_t state) +{ + int error = dpm_suspend_late(state); + + return error ? : dpm_suspend_noirq(state); +} +EXPORT_SYMBOL_GPL(dpm_suspend_end); /** * legacy_suspend - Execute a legacy (bus or class) suspend callback for device. diff --git a/drivers/xen/manage.c b/drivers/xen/manage.c index ce4fa0831860..9e14ae6cd49c 100644 --- a/drivers/xen/manage.c +++ b/drivers/xen/manage.c @@ -129,9 +129,9 @@ static void do_suspend(void) printk(KERN_DEBUG "suspending xenstore...\n"); xs_suspend(); - err = dpm_suspend_noirq(PMSG_FREEZE); + err = dpm_suspend_end(PMSG_FREEZE); if (err) { - printk(KERN_ERR "dpm_suspend_noirq failed: %d\n", err); + printk(KERN_ERR "dpm_suspend_end failed: %d\n", err); goto out_resume; } @@ -149,7 +149,7 @@ static void do_suspend(void) err = stop_machine(xen_suspend, &si, cpumask_of(0)); - dpm_resume_noirq(si.cancelled ? PMSG_THAW : PMSG_RESTORE); + dpm_resume_start(si.cancelled ? PMSG_THAW : PMSG_RESTORE); if (err) { printk(KERN_ERR "failed to start xen_suspend: %d\n", err); -- cgit v1.2.3 From e470d06655e00749f6f9372e4fa4f20cea7ed7c5 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Sun, 29 Jan 2012 20:38:41 +0100 Subject: PM / Sleep: Introduce generic callbacks for new device PM phases Introduce generic subsystem callbacks for the new phases of device suspend/resume during system power transitions: "late suspend", "early resume", "late freeze", "early thaw", "late poweroff", "early restore". Signed-off-by: Rafael J. Wysocki --- drivers/base/power/generic_ops.c | 157 ++++++++++++++++++++++++++------------- 1 file changed, 104 insertions(+), 53 deletions(-) (limited to 'drivers') diff --git a/drivers/base/power/generic_ops.c b/drivers/base/power/generic_ops.c index 10bdd793f0bd..d03d290f31c2 100644 --- a/drivers/base/power/generic_ops.c +++ b/drivers/base/power/generic_ops.c @@ -92,59 +92,28 @@ int pm_generic_prepare(struct device *dev) } /** - * __pm_generic_call - Generic suspend/freeze/poweroff/thaw subsystem callback. - * @dev: Device to handle. - * @event: PM transition of the system under way. - * @bool: Whether or not this is the "noirq" stage. - * - * Execute the PM callback corresponding to @event provided by the driver of - * @dev, if defined, and return its error code. Return 0 if the callback is - * not present. + * pm_generic_suspend_noirq - Generic suspend_noirq callback for subsystems. + * @dev: Device to suspend. */ -static int __pm_generic_call(struct device *dev, int event, bool noirq) +int pm_generic_suspend_noirq(struct device *dev) { const struct dev_pm_ops *pm = dev->driver ? dev->driver->pm : NULL; - int (*callback)(struct device *); - - if (!pm) - return 0; - - switch (event) { - case PM_EVENT_SUSPEND: - callback = noirq ? pm->suspend_noirq : pm->suspend; - break; - case PM_EVENT_FREEZE: - callback = noirq ? pm->freeze_noirq : pm->freeze; - break; - case PM_EVENT_HIBERNATE: - callback = noirq ? pm->poweroff_noirq : pm->poweroff; - break; - case PM_EVENT_RESUME: - callback = noirq ? pm->resume_noirq : pm->resume; - break; - case PM_EVENT_THAW: - callback = noirq ? pm->thaw_noirq : pm->thaw; - break; - case PM_EVENT_RESTORE: - callback = noirq ? pm->restore_noirq : pm->restore; - break; - default: - callback = NULL; - break; - } - return callback ? callback(dev) : 0; + return pm && pm->suspend_noirq ? pm->suspend_noirq(dev) : 0; } +EXPORT_SYMBOL_GPL(pm_generic_suspend_noirq); /** - * pm_generic_suspend_noirq - Generic suspend_noirq callback for subsystems. + * pm_generic_suspend_late - Generic suspend_late callback for subsystems. * @dev: Device to suspend. */ -int pm_generic_suspend_noirq(struct device *dev) +int pm_generic_suspend_late(struct device *dev) { - return __pm_generic_call(dev, PM_EVENT_SUSPEND, true); + const struct dev_pm_ops *pm = dev->driver ? dev->driver->pm : NULL; + + return pm && pm->suspend_late ? pm->suspend_late(dev) : 0; } -EXPORT_SYMBOL_GPL(pm_generic_suspend_noirq); +EXPORT_SYMBOL_GPL(pm_generic_suspend_late); /** * pm_generic_suspend - Generic suspend callback for subsystems. @@ -152,7 +121,9 @@ EXPORT_SYMBOL_GPL(pm_generic_suspend_noirq); */ int pm_generic_suspend(struct device *dev) { - return __pm_generic_call(dev, PM_EVENT_SUSPEND, false); + const struct dev_pm_ops *pm = dev->driver ? dev->driver->pm : NULL; + + return pm && pm->suspend ? pm->suspend(dev) : 0; } EXPORT_SYMBOL_GPL(pm_generic_suspend); @@ -162,17 +133,33 @@ EXPORT_SYMBOL_GPL(pm_generic_suspend); */ int pm_generic_freeze_noirq(struct device *dev) { - return __pm_generic_call(dev, PM_EVENT_FREEZE, true); + const struct dev_pm_ops *pm = dev->driver ? dev->driver->pm : NULL; + + return pm && pm->freeze_noirq ? pm->freeze_noirq(dev) : 0; } EXPORT_SYMBOL_GPL(pm_generic_freeze_noirq); +/** + * pm_generic_freeze_late - Generic freeze_late callback for subsystems. + * @dev: Device to freeze. + */ +int pm_generic_freeze_late(struct device *dev) +{ + const struct dev_pm_ops *pm = dev->driver ? dev->driver->pm : NULL; + + return pm && pm->freeze_late ? pm->freeze_late(dev) : 0; +} +EXPORT_SYMBOL_GPL(pm_generic_freeze_late); + /** * pm_generic_freeze - Generic freeze callback for subsystems. * @dev: Device to freeze. */ int pm_generic_freeze(struct device *dev) { - return __pm_generic_call(dev, PM_EVENT_FREEZE, false); + const struct dev_pm_ops *pm = dev->driver ? dev->driver->pm : NULL; + + return pm && pm->freeze ? pm->freeze(dev) : 0; } EXPORT_SYMBOL_GPL(pm_generic_freeze); @@ -182,17 +169,33 @@ EXPORT_SYMBOL_GPL(pm_generic_freeze); */ int pm_generic_poweroff_noirq(struct device *dev) { - return __pm_generic_call(dev, PM_EVENT_HIBERNATE, true); + const struct dev_pm_ops *pm = dev->driver ? dev->driver->pm : NULL; + + return pm && pm->poweroff_noirq ? pm->poweroff_noirq(dev) : 0; } EXPORT_SYMBOL_GPL(pm_generic_poweroff_noirq); +/** + * pm_generic_poweroff_late - Generic poweroff_late callback for subsystems. + * @dev: Device to handle. + */ +int pm_generic_poweroff_late(struct device *dev) +{ + const struct dev_pm_ops *pm = dev->driver ? dev->driver->pm : NULL; + + return pm && pm->poweroff_late ? pm->poweroff_late(dev) : 0; +} +EXPORT_SYMBOL_GPL(pm_generic_poweroff_late); + /** * pm_generic_poweroff - Generic poweroff callback for subsystems. * @dev: Device to handle. */ int pm_generic_poweroff(struct device *dev) { - return __pm_generic_call(dev, PM_EVENT_HIBERNATE, false); + const struct dev_pm_ops *pm = dev->driver ? dev->driver->pm : NULL; + + return pm && pm->poweroff ? pm->poweroff(dev) : 0; } EXPORT_SYMBOL_GPL(pm_generic_poweroff); @@ -202,17 +205,33 @@ EXPORT_SYMBOL_GPL(pm_generic_poweroff); */ int pm_generic_thaw_noirq(struct device *dev) { - return __pm_generic_call(dev, PM_EVENT_THAW, true); + const struct dev_pm_ops *pm = dev->driver ? dev->driver->pm : NULL; + + return pm && pm->thaw_noirq ? pm->thaw_noirq(dev) : 0; } EXPORT_SYMBOL_GPL(pm_generic_thaw_noirq); +/** + * pm_generic_thaw_early - Generic thaw_early callback for subsystems. + * @dev: Device to thaw. + */ +int pm_generic_thaw_early(struct device *dev) +{ + const struct dev_pm_ops *pm = dev->driver ? dev->driver->pm : NULL; + + return pm && pm->thaw_early ? pm->thaw_early(dev) : 0; +} +EXPORT_SYMBOL_GPL(pm_generic_thaw_early); + /** * pm_generic_thaw - Generic thaw callback for subsystems. * @dev: Device to thaw. */ int pm_generic_thaw(struct device *dev) { - return __pm_generic_call(dev, PM_EVENT_THAW, false); + const struct dev_pm_ops *pm = dev->driver ? dev->driver->pm : NULL; + + return pm && pm->thaw ? pm->thaw(dev) : 0; } EXPORT_SYMBOL_GPL(pm_generic_thaw); @@ -222,17 +241,33 @@ EXPORT_SYMBOL_GPL(pm_generic_thaw); */ int pm_generic_resume_noirq(struct device *dev) { - return __pm_generic_call(dev, PM_EVENT_RESUME, true); + const struct dev_pm_ops *pm = dev->driver ? dev->driver->pm : NULL; + + return pm && pm->resume_noirq ? pm->resume_noirq(dev) : 0; } EXPORT_SYMBOL_GPL(pm_generic_resume_noirq); +/** + * pm_generic_resume_early - Generic resume_early callback for subsystems. + * @dev: Device to resume. + */ +int pm_generic_resume_early(struct device *dev) +{ + const struct dev_pm_ops *pm = dev->driver ? dev->driver->pm : NULL; + + return pm && pm->resume_early ? pm->resume_early(dev) : 0; +} +EXPORT_SYMBOL_GPL(pm_generic_resume_early); + /** * pm_generic_resume - Generic resume callback for subsystems. * @dev: Device to resume. */ int pm_generic_resume(struct device *dev) { - return __pm_generic_call(dev, PM_EVENT_RESUME, false); + const struct dev_pm_ops *pm = dev->driver ? dev->driver->pm : NULL; + + return pm && pm->resume ? pm->resume(dev) : 0; } EXPORT_SYMBOL_GPL(pm_generic_resume); @@ -242,17 +277,33 @@ EXPORT_SYMBOL_GPL(pm_generic_resume); */ int pm_generic_restore_noirq(struct device *dev) { - return __pm_generic_call(dev, PM_EVENT_RESTORE, true); + const struct dev_pm_ops *pm = dev->driver ? dev->driver->pm : NULL; + + return pm && pm->restore_noirq ? pm->restore_noirq(dev) : 0; } EXPORT_SYMBOL_GPL(pm_generic_restore_noirq); +/** + * pm_generic_restore_early - Generic restore_early callback for subsystems. + * @dev: Device to resume. + */ +int pm_generic_restore_early(struct device *dev) +{ + const struct dev_pm_ops *pm = dev->driver ? dev->driver->pm : NULL; + + return pm && pm->restore_early ? pm->restore_early(dev) : 0; +} +EXPORT_SYMBOL_GPL(pm_generic_restore_early); + /** * pm_generic_restore - Generic restore callback for subsystems. * @dev: Device to restore. */ int pm_generic_restore(struct device *dev) { - return __pm_generic_call(dev, PM_EVENT_RESTORE, false); + const struct dev_pm_ops *pm = dev->driver ? dev->driver->pm : NULL; + + return pm && pm->restore ? pm->restore(dev) : 0; } EXPORT_SYMBOL_GPL(pm_generic_restore); -- cgit v1.2.3 From 0496c8ae366724a0a2136cec09a2e277e782c126 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Sun, 29 Jan 2012 20:39:02 +0100 Subject: PM / Domains: Run late/early device suspend callbacks at the right time After the introduction of the late/early phases of device suspend/resume during system-wide power transitions it is possible to make the generic PM domains code execute its default late/early device suspend/resume callbacks during those phases instead of the corresponding _noirq phases. The _noirq device suspend/resume phases were only used for executing those callbacks, because this was the only way it could be done, but now we can do better. Signed-off-by: Rafael J. Wysocki --- drivers/base/power/domain.c | 157 +++++++++++++++++++++++++++++++------------- 1 file changed, 111 insertions(+), 46 deletions(-) (limited to 'drivers') diff --git a/drivers/base/power/domain.c b/drivers/base/power/domain.c index 978bbf7ac6af..12a03afe5305 100644 --- a/drivers/base/power/domain.c +++ b/drivers/base/power/domain.c @@ -820,17 +820,16 @@ static int pm_genpd_suspend(struct device *dev) } /** - * pm_genpd_suspend_noirq - Late suspend of a device from an I/O PM domain. + * pm_genpd_suspend_late - Late suspend of a device from an I/O PM domain. * @dev: Device to suspend. * * Carry out a late suspend of a device under the assumption that its * pm_domain field points to the domain member of an object of type * struct generic_pm_domain representing a PM domain consisting of I/O devices. */ -static int pm_genpd_suspend_noirq(struct device *dev) +static int pm_genpd_suspend_late(struct device *dev) { struct generic_pm_domain *genpd; - int ret; dev_dbg(dev, "%s()\n", __func__); @@ -838,14 +837,28 @@ static int pm_genpd_suspend_noirq(struct device *dev) if (IS_ERR(genpd)) return -EINVAL; - if (genpd->suspend_power_off) - return 0; + return genpd->suspend_power_off ? 0 : genpd_suspend_late(genpd, dev); +} - ret = genpd_suspend_late(genpd, dev); - if (ret) - return ret; +/** + * pm_genpd_suspend_noirq - Completion of suspend of device in an I/O PM domain. + * @dev: Device to suspend. + * + * Stop the device and remove power from the domain if all devices in it have + * been stopped. + */ +static int pm_genpd_suspend_noirq(struct device *dev) +{ + struct generic_pm_domain *genpd; + + dev_dbg(dev, "%s()\n", __func__); + + genpd = dev_to_genpd(dev); + if (IS_ERR(genpd)) + return -EINVAL; - if (dev->power.wakeup_path && genpd_dev_active_wakeup(genpd, dev)) + if (genpd->suspend_power_off + || (dev->power.wakeup_path && genpd_dev_active_wakeup(genpd, dev))) return 0; genpd_stop_dev(genpd, dev); @@ -862,13 +875,10 @@ static int pm_genpd_suspend_noirq(struct device *dev) } /** - * pm_genpd_resume_noirq - Early resume of a device from an I/O power domain. + * pm_genpd_resume_noirq - Start of resume of device in an I/O PM domain. * @dev: Device to resume. * - * Carry out an early resume of a device under the assumption that its - * pm_domain field points to the domain member of an object of type - * struct generic_pm_domain representing a power domain consisting of I/O - * devices. + * Restore power to the device's PM domain, if necessary, and start the device. */ static int pm_genpd_resume_noirq(struct device *dev) { @@ -890,13 +900,34 @@ static int pm_genpd_resume_noirq(struct device *dev) */ pm_genpd_poweron(genpd); genpd->suspended_count--; - genpd_start_dev(genpd, dev); - return genpd_resume_early(genpd, dev); + return genpd_start_dev(genpd, dev); } /** - * pm_genpd_resume - Resume a device belonging to an I/O power domain. + * pm_genpd_resume_early - Early resume of a device in an I/O PM domain. + * @dev: Device to resume. + * + * Carry out an early resume of a device under the assumption that its + * pm_domain field points to the domain member of an object of type + * struct generic_pm_domain representing a power domain consisting of I/O + * devices. + */ +static int pm_genpd_resume_early(struct device *dev) +{ + struct generic_pm_domain *genpd; + + dev_dbg(dev, "%s()\n", __func__); + + genpd = dev_to_genpd(dev); + if (IS_ERR(genpd)) + return -EINVAL; + + return genpd->suspend_power_off ? 0 : genpd_resume_early(genpd, dev); +} + +/** + * pm_genpd_resume - Resume of device in an I/O PM domain. * @dev: Device to resume. * * Resume a device under the assumption that its pm_domain field points to the @@ -917,7 +948,7 @@ static int pm_genpd_resume(struct device *dev) } /** - * pm_genpd_freeze - Freeze a device belonging to an I/O power domain. + * pm_genpd_freeze - Freezing a device in an I/O PM domain. * @dev: Device to freeze. * * Freeze a device under the assumption that its pm_domain field points to the @@ -938,7 +969,29 @@ static int pm_genpd_freeze(struct device *dev) } /** - * pm_genpd_freeze_noirq - Late freeze of a device from an I/O power domain. + * pm_genpd_freeze_late - Late freeze of a device in an I/O PM domain. + * @dev: Device to freeze. + * + * Carry out a late freeze of a device under the assumption that its + * pm_domain field points to the domain member of an object of type + * struct generic_pm_domain representing a power domain consisting of I/O + * devices. + */ +static int pm_genpd_freeze_late(struct device *dev) +{ + struct generic_pm_domain *genpd; + + dev_dbg(dev, "%s()\n", __func__); + + genpd = dev_to_genpd(dev); + if (IS_ERR(genpd)) + return -EINVAL; + + return genpd->suspend_power_off ? 0 : genpd_freeze_late(genpd, dev); +} + +/** + * pm_genpd_freeze_noirq - Completion of freezing a device in an I/O PM domain. * @dev: Device to freeze. * * Carry out a late freeze of a device under the assumption that its @@ -949,7 +1002,6 @@ static int pm_genpd_freeze(struct device *dev) static int pm_genpd_freeze_noirq(struct device *dev) { struct generic_pm_domain *genpd; - int ret; dev_dbg(dev, "%s()\n", __func__); @@ -957,20 +1009,31 @@ static int pm_genpd_freeze_noirq(struct device *dev) if (IS_ERR(genpd)) return -EINVAL; - if (genpd->suspend_power_off) - return 0; + return genpd->suspend_power_off ? 0 : genpd_stop_dev(genpd, dev); +} - ret = genpd_freeze_late(genpd, dev); - if (ret) - return ret; +/** + * pm_genpd_thaw_noirq - Early thaw of device in an I/O PM domain. + * @dev: Device to thaw. + * + * Start the device, unless power has been removed from the domain already + * before the system transition. + */ +static int pm_genpd_thaw_noirq(struct device *dev) +{ + struct generic_pm_domain *genpd; - genpd_stop_dev(genpd, dev); + dev_dbg(dev, "%s()\n", __func__); - return 0; + genpd = dev_to_genpd(dev); + if (IS_ERR(genpd)) + return -EINVAL; + + return genpd->suspend_power_off ? 0 : genpd_start_dev(genpd, dev); } /** - * pm_genpd_thaw_noirq - Early thaw of a device from an I/O power domain. + * pm_genpd_thaw_early - Early thaw of device in an I/O PM domain. * @dev: Device to thaw. * * Carry out an early thaw of a device under the assumption that its @@ -978,7 +1041,7 @@ static int pm_genpd_freeze_noirq(struct device *dev) * struct generic_pm_domain representing a power domain consisting of I/O * devices. */ -static int pm_genpd_thaw_noirq(struct device *dev) +static int pm_genpd_thaw_early(struct device *dev) { struct generic_pm_domain *genpd; @@ -988,12 +1051,7 @@ static int pm_genpd_thaw_noirq(struct device *dev) if (IS_ERR(genpd)) return -EINVAL; - if (genpd->suspend_power_off) - return 0; - - genpd_start_dev(genpd, dev); - - return genpd_thaw_early(genpd, dev); + return genpd->suspend_power_off ? 0 : genpd_thaw_early(genpd, dev); } /** @@ -1018,13 +1076,11 @@ static int pm_genpd_thaw(struct device *dev) } /** - * pm_genpd_restore_noirq - Early restore of a device from an I/O power domain. + * pm_genpd_restore_noirq - Start of restore of device in an I/O PM domain. * @dev: Device to resume. * - * Carry out an early restore of a device under the assumption that its - * pm_domain field points to the domain member of an object of type - * struct generic_pm_domain representing a power domain consisting of I/O - * devices. + * Make sure the domain will be in the same power state as before the + * hibernation the system is resuming from and start the device if necessary. */ static int pm_genpd_restore_noirq(struct device *dev) { @@ -1054,9 +1110,8 @@ static int pm_genpd_restore_noirq(struct device *dev) pm_genpd_poweron(genpd); genpd->suspended_count--; - genpd_start_dev(genpd, dev); - return genpd_resume_early(genpd, dev); + return genpd_start_dev(genpd, dev); } /** @@ -1099,11 +1154,15 @@ static void pm_genpd_complete(struct device *dev) #define pm_genpd_prepare NULL #define pm_genpd_suspend NULL +#define pm_genpd_suspend_late NULL #define pm_genpd_suspend_noirq NULL +#define pm_genpd_resume_early NULL #define pm_genpd_resume_noirq NULL #define pm_genpd_resume NULL #define pm_genpd_freeze NULL +#define pm_genpd_freeze_late NULL #define pm_genpd_freeze_noirq NULL +#define pm_genpd_thaw_early NULL #define pm_genpd_thaw_noirq NULL #define pm_genpd_thaw NULL #define pm_genpd_restore_noirq NULL @@ -1450,7 +1509,7 @@ static int pm_genpd_default_suspend_late(struct device *dev) { int (*cb)(struct device *__dev) = dev_gpd_data(dev)->ops.suspend_late; - return cb ? cb(dev) : pm_generic_suspend_noirq(dev); + return cb ? cb(dev) : pm_generic_suspend_late(dev); } /** @@ -1461,7 +1520,7 @@ static int pm_genpd_default_resume_early(struct device *dev) { int (*cb)(struct device *__dev) = dev_gpd_data(dev)->ops.resume_early; - return cb ? cb(dev) : pm_generic_resume_noirq(dev); + return cb ? cb(dev) : pm_generic_resume_early(dev); } /** @@ -1494,7 +1553,7 @@ static int pm_genpd_default_freeze_late(struct device *dev) { int (*cb)(struct device *__dev) = dev_gpd_data(dev)->ops.freeze_late; - return cb ? cb(dev) : pm_generic_freeze_noirq(dev); + return cb ? cb(dev) : pm_generic_freeze_late(dev); } /** @@ -1505,7 +1564,7 @@ static int pm_genpd_default_thaw_early(struct device *dev) { int (*cb)(struct device *__dev) = dev_gpd_data(dev)->ops.thaw_early; - return cb ? cb(dev) : pm_generic_thaw_noirq(dev); + return cb ? cb(dev) : pm_generic_thaw_early(dev); } /** @@ -1564,16 +1623,22 @@ void pm_genpd_init(struct generic_pm_domain *genpd, genpd->domain.ops.runtime_idle = pm_generic_runtime_idle; genpd->domain.ops.prepare = pm_genpd_prepare; genpd->domain.ops.suspend = pm_genpd_suspend; + genpd->domain.ops.suspend_late = pm_genpd_suspend_late; genpd->domain.ops.suspend_noirq = pm_genpd_suspend_noirq; genpd->domain.ops.resume_noirq = pm_genpd_resume_noirq; + genpd->domain.ops.resume_early = pm_genpd_resume_early; genpd->domain.ops.resume = pm_genpd_resume; genpd->domain.ops.freeze = pm_genpd_freeze; + genpd->domain.ops.freeze_late = pm_genpd_freeze_late; genpd->domain.ops.freeze_noirq = pm_genpd_freeze_noirq; genpd->domain.ops.thaw_noirq = pm_genpd_thaw_noirq; + genpd->domain.ops.thaw_early = pm_genpd_thaw_early; genpd->domain.ops.thaw = pm_genpd_thaw; genpd->domain.ops.poweroff = pm_genpd_suspend; + genpd->domain.ops.poweroff_late = pm_genpd_suspend_late; genpd->domain.ops.poweroff_noirq = pm_genpd_suspend_noirq; genpd->domain.ops.restore_noirq = pm_genpd_restore_noirq; + genpd->domain.ops.restore_early = pm_genpd_resume_early; genpd->domain.ops.restore = pm_genpd_resume; genpd->domain.ops.complete = pm_genpd_complete; genpd->dev_ops.save_state = pm_genpd_default_save_state; -- cgit v1.2.3 From 7c95149b7f1f61201b12c73c4862a41bf2428961 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Sat, 11 Feb 2012 00:00:11 +0100 Subject: PM / Sleep: Initialize wakeup source locks in wakeup_source_add() Initialize wakeup source locks in wakeup_source_add() instead of wakeup_source_create(), because otherwise the locks of the wakeup sources that haven't been allocated with wakeup_source_create() aren't initialized and handled properly. Signed-off-by: Rafael J. Wysocki --- drivers/base/power/wakeup.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/base/power/wakeup.c b/drivers/base/power/wakeup.c index caf995fb774b..6e591a8a49da 100644 --- a/drivers/base/power/wakeup.c +++ b/drivers/base/power/wakeup.c @@ -64,7 +64,6 @@ struct wakeup_source *wakeup_source_create(const char *name) if (!ws) return NULL; - spin_lock_init(&ws->lock); if (name) ws->name = kstrdup(name, GFP_KERNEL); @@ -105,6 +104,7 @@ void wakeup_source_add(struct wakeup_source *ws) if (WARN_ON(!ws)) return; + spin_lock_init(&ws->lock); setup_timer(&ws->timer, pm_wakeup_timer_fn, (unsigned long)ws); ws->active = false; -- cgit v1.2.3 From d94aff87826ee6aa43032f4c0263482913f4e2c8 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Fri, 17 Feb 2012 23:39:20 +0100 Subject: PM / Sleep: Fix possible infinite loop during wakeup source destruction MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit If wakeup_source_destroy() is called for an active wakeup source that is never deactivated, it will spin forever. To prevent that from happening, make wakeup_source_destroy() call __pm_relax() for the wakeup source object it is about to free instead of waiting until it will be deactivated by someone else. However, for this to work it also needs to make sure that the timer function will not be executed after the final __pm_relax(), so make it run del_timer_sync() on the wakeup source's timer beforehand. Additionally, update the kerneldoc comment to document the requirement that __pm_stay_awake() and __pm_wakeup_event() must not be run in parallel with wakeup_source_destroy(). Reported-by: Arve Hjønnevåg Signed-off-by: Rafael J. Wysocki --- drivers/base/power/wakeup.c | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) (limited to 'drivers') diff --git a/drivers/base/power/wakeup.c b/drivers/base/power/wakeup.c index 6e591a8a49da..d279f462d624 100644 --- a/drivers/base/power/wakeup.c +++ b/drivers/base/power/wakeup.c @@ -74,22 +74,17 @@ EXPORT_SYMBOL_GPL(wakeup_source_create); /** * wakeup_source_destroy - Destroy a struct wakeup_source object. * @ws: Wakeup source to destroy. + * + * Callers must ensure that __pm_stay_awake() or __pm_wakeup_event() will never + * be run in parallel with this function for the same wakeup source object. */ void wakeup_source_destroy(struct wakeup_source *ws) { if (!ws) return; - spin_lock_irq(&ws->lock); - while (ws->active) { - spin_unlock_irq(&ws->lock); - - schedule_timeout_interruptible(msecs_to_jiffies(TIMEOUT)); - - spin_lock_irq(&ws->lock); - } - spin_unlock_irq(&ws->lock); - + del_timer_sync(&ws->timer); + __pm_relax(ws); kfree(ws->name); kfree(ws); } -- cgit v1.2.3 From da863cddd831b0f4bf2d067f8b75254f1be94590 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Fri, 17 Feb 2012 23:39:33 +0100 Subject: PM / Sleep: Fix race conditions related to wakeup source timer function MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit If __pm_wakeup_event() has been used (with a nonzero timeout) to report a wakeup event and then __pm_relax() immediately followed by __pm_stay_awake() is called or __pm_wakeup_event() is called once again for the same wakeup source object before its timer expires, the timer function pm_wakeup_timer_fn() may still be run as a result of the previous __pm_wakeup_event() call. In either of those cases it may mistakenly deactivate the wakeup source that has just been activated. To prevent that from happening, make wakeup_source_deactivate() clear the wakeup source's timer_expires field and make pm_wakeup_timer_fn() check if timer_expires is different from zero and if it's not in future before calling wakeup_source_deactivate() (if timer_expires is 0, it means that the timer has just been deleted and if timer_expires is in future, it means that the timer has just been rescheduled to a different time). Reported-by: Arve Hjønnevåg Signed-off-by: Rafael J. Wysocki --- drivers/base/power/wakeup.c | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/base/power/wakeup.c b/drivers/base/power/wakeup.c index d279f462d624..b38bb9afb719 100644 --- a/drivers/base/power/wakeup.c +++ b/drivers/base/power/wakeup.c @@ -433,6 +433,7 @@ static void wakeup_source_deactivate(struct wakeup_source *ws) ws->max_time = duration; del_timer(&ws->timer); + ws->timer_expires = 0; /* * Increment the counter of registered wakeup events and decrement the @@ -487,11 +488,22 @@ EXPORT_SYMBOL_GPL(pm_relax); * pm_wakeup_timer_fn - Delayed finalization of a wakeup event. * @data: Address of the wakeup source object associated with the event source. * - * Call __pm_relax() for the wakeup source whose address is stored in @data. + * Call wakeup_source_deactivate() for the wakeup source whose address is stored + * in @data if it is currently active and its timer has not been canceled and + * the expiration time of the timer is not in future. */ static void pm_wakeup_timer_fn(unsigned long data) { - __pm_relax((struct wakeup_source *)data); + struct wakeup_source *ws = (struct wakeup_source *)data; + unsigned long flags; + + spin_lock_irqsave(&ws->lock, flags); + + if (ws->active && ws->timer_expires + && time_after_eq(jiffies, ws->timer_expires)) + wakeup_source_deactivate(ws); + + spin_unlock_irqrestore(&ws->lock, flags); } /** -- cgit v1.2.3 From 4782e1654bdbd30cf307da090b3c4f70157477cb Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Fri, 17 Feb 2012 23:39:39 +0100 Subject: PM / Sleep: Make __pm_stay_awake() delete wakeup source timers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit If __pm_stay_awake() is called after __pm_wakeup_event() for the same wakep source object before its timer expires, it won't cancel the timer, so the wakeup source will be deactivated from the timer function as scheduled by __pm_wakeup_event(). In that case __pm_stay_awake() doesn't have any effect beyond incrementing the wakeup source's event_count field, although it should cancel the timer and make the wakeup source stay active until __pm_relax() is called for it. To fix this problem make __pm_stay_awake() delete the wakeup source's timer and ensure that it won't be deactivated from the timer funtion afterwards by clearing its timer_expires field. Reported-by: Arve Hjønnevåg Signed-off-by: Rafael J. Wysocki --- drivers/base/power/wakeup.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/base/power/wakeup.c b/drivers/base/power/wakeup.c index b38bb9afb719..7c5ab70b9ef3 100644 --- a/drivers/base/power/wakeup.c +++ b/drivers/base/power/wakeup.c @@ -344,7 +344,6 @@ static void wakeup_source_activate(struct wakeup_source *ws) { ws->active = true; ws->active_count++; - ws->timer_expires = jiffies; ws->last_time = ktime_get(); /* Increment the counter of events in progress. */ @@ -365,9 +364,14 @@ void __pm_stay_awake(struct wakeup_source *ws) return; spin_lock_irqsave(&ws->lock, flags); + ws->event_count++; if (!ws->active) wakeup_source_activate(ws); + + del_timer(&ws->timer); + ws->timer_expires = 0; + spin_unlock_irqrestore(&ws->lock, flags); } EXPORT_SYMBOL_GPL(__pm_stay_awake); @@ -541,7 +545,7 @@ void __pm_wakeup_event(struct wakeup_source *ws, unsigned int msec) if (!expires) expires = 1; - if (time_after(expires, ws->timer_expires)) { + if (!ws->timer_expires || time_after(expires, ws->timer_expires)) { mod_timer(&ws->timer, expires); ws->timer_expires = expires; } -- cgit v1.2.3 From 8671bbc1bd0442ef0eab27f7d56216431c490820 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Tue, 21 Feb 2012 23:47:56 +0100 Subject: PM / Sleep: Add more wakeup source initialization routines MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The existing wakeup source initialization routines are not particularly useful for wakeup sources that aren't created by wakeup_source_create(), because their users have to open code filling the objects with zeros and setting their names. For this reason, introduce routines that can be used for initializing, for example, static wakeup source objects. Requested-by: Arve Hjønnevåg Signed-off-by: Rafael J. Wysocki --- drivers/base/power/wakeup.c | 50 +++++++++++++++++++++++++++++++++++++-------- 1 file changed, 41 insertions(+), 9 deletions(-) (limited to 'drivers') diff --git a/drivers/base/power/wakeup.c b/drivers/base/power/wakeup.c index 7c5ab70b9ef3..2a3e581b8dcd 100644 --- a/drivers/base/power/wakeup.c +++ b/drivers/base/power/wakeup.c @@ -52,6 +52,23 @@ static void pm_wakeup_timer_fn(unsigned long data); static LIST_HEAD(wakeup_sources); +/** + * wakeup_source_prepare - Prepare a new wakeup source for initialization. + * @ws: Wakeup source to prepare. + * @name: Pointer to the name of the new wakeup source. + * + * Callers must ensure that the @name string won't be freed when @ws is still in + * use. + */ +void wakeup_source_prepare(struct wakeup_source *ws, const char *name) +{ + if (ws) { + memset(ws, 0, sizeof(*ws)); + ws->name = name; + } +} +EXPORT_SYMBOL_GPL(wakeup_source_prepare); + /** * wakeup_source_create - Create a struct wakeup_source object. * @name: Name of the new wakeup source. @@ -60,31 +77,44 @@ struct wakeup_source *wakeup_source_create(const char *name) { struct wakeup_source *ws; - ws = kzalloc(sizeof(*ws), GFP_KERNEL); + ws = kmalloc(sizeof(*ws), GFP_KERNEL); if (!ws) return NULL; - if (name) - ws->name = kstrdup(name, GFP_KERNEL); - + wakeup_source_prepare(ws, name ? kstrdup(name, GFP_KERNEL) : NULL); return ws; } EXPORT_SYMBOL_GPL(wakeup_source_create); /** - * wakeup_source_destroy - Destroy a struct wakeup_source object. - * @ws: Wakeup source to destroy. + * wakeup_source_drop - Prepare a struct wakeup_source object for destruction. + * @ws: Wakeup source to prepare for destruction. * * Callers must ensure that __pm_stay_awake() or __pm_wakeup_event() will never * be run in parallel with this function for the same wakeup source object. */ -void wakeup_source_destroy(struct wakeup_source *ws) +void wakeup_source_drop(struct wakeup_source *ws) { if (!ws) return; del_timer_sync(&ws->timer); __pm_relax(ws); +} +EXPORT_SYMBOL_GPL(wakeup_source_drop); + +/** + * wakeup_source_destroy - Destroy a struct wakeup_source object. + * @ws: Wakeup source to destroy. + * + * Use only for wakeup source objects created with wakeup_source_create(). + */ +void wakeup_source_destroy(struct wakeup_source *ws) +{ + if (!ws) + return; + + wakeup_source_drop(ws); kfree(ws->name); kfree(ws); } @@ -147,8 +177,10 @@ EXPORT_SYMBOL_GPL(wakeup_source_register); */ void wakeup_source_unregister(struct wakeup_source *ws) { - wakeup_source_remove(ws); - wakeup_source_destroy(ws); + if (ws) { + wakeup_source_remove(ws); + wakeup_source_destroy(ws); + } } EXPORT_SYMBOL_GPL(wakeup_source_unregister); -- cgit v1.2.3 From 85dc0b8a4019e38ad4fd0c008f89a5c241805ac2 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Tue, 13 Mar 2012 01:01:39 +0100 Subject: PM / QoS: Make it possible to expose PM QoS latency constraints A runtime suspend of a device (e.g. an MMC controller) belonging to a power domain or, in a more complicated scenario, a runtime suspend of another device in the same power domain, may cause power to be removed from the entire domain. In that case, the amount of time necessary to runtime-resume the given device (e.g. the MMC controller) is often substantially greater than the time needed to run its driver's runtime resume callback. That may hurt performance in some situations, because user data may need to wait for the device to become operational, so we should make it possible to prevent that from happening. For this reason, introduce a new sysfs attribute for devices, power/pm_qos_resume_latency_us, allowing user space to specify the upper bound of the time necessary to bring the (runtime-suspended) device up after the resume of it has been requested. However, make that attribute appear only for the devices whose drivers declare support for it by calling the (new) dev_pm_qos_expose_latency_limit() helper function with the appropriate initial value of the attribute. Signed-off-by: Rafael J. Wysocki Reviewed-by: Kevin Hilman Reviewed-by: Mark Brown Acked-by: Linus Walleij --- drivers/base/power/power.h | 4 +++ drivers/base/power/qos.c | 61 ++++++++++++++++++++++++++++++++++++++++++++++ drivers/base/power/sysfs.c | 47 +++++++++++++++++++++++++++++++++++ 3 files changed, 112 insertions(+) (limited to 'drivers') diff --git a/drivers/base/power/power.h b/drivers/base/power/power.h index 9bf62323aaf3..eeb4bff9505c 100644 --- a/drivers/base/power/power.h +++ b/drivers/base/power/power.h @@ -71,6 +71,8 @@ extern void dpm_sysfs_remove(struct device *dev); extern void rpm_sysfs_remove(struct device *dev); extern int wakeup_sysfs_add(struct device *dev); extern void wakeup_sysfs_remove(struct device *dev); +extern int pm_qos_sysfs_add(struct device *dev); +extern void pm_qos_sysfs_remove(struct device *dev); #else /* CONFIG_PM */ @@ -79,5 +81,7 @@ static inline void dpm_sysfs_remove(struct device *dev) {} static inline void rpm_sysfs_remove(struct device *dev) {} static inline int wakeup_sysfs_add(struct device *dev) { return 0; } static inline void wakeup_sysfs_remove(struct device *dev) {} +static inline int pm_qos_sysfs_add(struct device *dev) { return 0; } +static inline void pm_qos_sysfs_remove(struct device *dev) {} #endif diff --git a/drivers/base/power/qos.c b/drivers/base/power/qos.c index c5d358837461..71855570922d 100644 --- a/drivers/base/power/qos.c +++ b/drivers/base/power/qos.c @@ -41,6 +41,7 @@ #include #include +#include "power.h" static DEFINE_MUTEX(dev_pm_qos_mtx); @@ -166,6 +167,12 @@ void dev_pm_qos_constraints_destroy(struct device *dev) struct dev_pm_qos_request *req, *tmp; struct pm_qos_constraints *c; + /* + * If the device's PM QoS resume latency limit has been exposed to user + * space, it has to be hidden at this point. + */ + dev_pm_qos_hide_latency_limit(dev); + mutex_lock(&dev_pm_qos_mtx); dev->power.power_state = PMSG_INVALID; @@ -445,3 +452,57 @@ int dev_pm_qos_add_ancestor_request(struct device *dev, return error; } EXPORT_SYMBOL_GPL(dev_pm_qos_add_ancestor_request); + +#ifdef CONFIG_PM_RUNTIME +static void __dev_pm_qos_drop_user_request(struct device *dev) +{ + dev_pm_qos_remove_request(dev->power.pq_req); + dev->power.pq_req = 0; +} + +/** + * dev_pm_qos_expose_latency_limit - Expose PM QoS latency limit to user space. + * @dev: Device whose PM QoS latency limit is to be exposed to user space. + * @value: Initial value of the latency limit. + */ +int dev_pm_qos_expose_latency_limit(struct device *dev, s32 value) +{ + struct dev_pm_qos_request *req; + int ret; + + if (!device_is_registered(dev) || value < 0) + return -EINVAL; + + if (dev->power.pq_req) + return -EEXIST; + + req = kzalloc(sizeof(*req), GFP_KERNEL); + if (!req) + return -ENOMEM; + + ret = dev_pm_qos_add_request(dev, req, value); + if (ret < 0) + return ret; + + dev->power.pq_req = req; + ret = pm_qos_sysfs_add(dev); + if (ret) + __dev_pm_qos_drop_user_request(dev); + + return ret; +} +EXPORT_SYMBOL_GPL(dev_pm_qos_expose_latency_limit); + +/** + * dev_pm_qos_hide_latency_limit - Hide PM QoS latency limit from user space. + * @dev: Device whose PM QoS latency limit is to be hidden from user space. + */ +void dev_pm_qos_hide_latency_limit(struct device *dev) +{ + if (dev->power.pq_req) { + pm_qos_sysfs_remove(dev); + __dev_pm_qos_drop_user_request(dev); + } +} +EXPORT_SYMBOL_GPL(dev_pm_qos_hide_latency_limit); +#endif /* CONFIG_PM_RUNTIME */ diff --git a/drivers/base/power/sysfs.c b/drivers/base/power/sysfs.c index adf41be0ea66..95c12f6cb5b9 100644 --- a/drivers/base/power/sysfs.c +++ b/drivers/base/power/sysfs.c @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -217,6 +218,31 @@ static ssize_t autosuspend_delay_ms_store(struct device *dev, static DEVICE_ATTR(autosuspend_delay_ms, 0644, autosuspend_delay_ms_show, autosuspend_delay_ms_store); +static ssize_t pm_qos_latency_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sprintf(buf, "%d\n", dev->power.pq_req->node.prio); +} + +static ssize_t pm_qos_latency_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t n) +{ + s32 value; + int ret; + + if (kstrtos32(buf, 0, &value)) + return -EINVAL; + + if (value < 0) + return -EINVAL; + + ret = dev_pm_qos_update_request(dev->power.pq_req, value); + return ret < 0 ? ret : n; +} + +static DEVICE_ATTR(pm_qos_resume_latency_us, 0644, + pm_qos_latency_show, pm_qos_latency_store); #endif /* CONFIG_PM_RUNTIME */ #ifdef CONFIG_PM_SLEEP @@ -490,6 +516,17 @@ static struct attribute_group pm_runtime_attr_group = { .attrs = runtime_attrs, }; +static struct attribute *pm_qos_attrs[] = { +#ifdef CONFIG_PM_RUNTIME + &dev_attr_pm_qos_resume_latency_us.attr, +#endif /* CONFIG_PM_RUNTIME */ + NULL, +}; +static struct attribute_group pm_qos_attr_group = { + .name = power_group_name, + .attrs = pm_qos_attrs, +}; + int dpm_sysfs_add(struct device *dev) { int rc; @@ -530,6 +567,16 @@ void wakeup_sysfs_remove(struct device *dev) sysfs_unmerge_group(&dev->kobj, &pm_wakeup_attr_group); } +int pm_qos_sysfs_add(struct device *dev) +{ + return sysfs_merge_group(&dev->kobj, &pm_qos_attr_group); +} + +void pm_qos_sysfs_remove(struct device *dev) +{ + sysfs_unmerge_group(&dev->kobj, &pm_qos_attr_group); +} + void rpm_sysfs_remove(struct device *dev) { sysfs_unmerge_group(&dev->kobj, &pm_runtime_attr_group); -- cgit v1.2.3 From c419e611c3c59c0e48405c279e497c4bf1bf2cd2 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Tue, 13 Mar 2012 01:01:51 +0100 Subject: tmio_mmc / PM: Use PM QoS latency constraint Make tmio_mmc call dev_pm_qos_expose_latency_limit() to expose the PM QoS latency limit to user space and specify the initial value of it as 100 microseconds. Signed-off-by: Rafael J. Wysocki --- drivers/mmc/host/tmio_mmc_pio.c | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'drivers') diff --git a/drivers/mmc/host/tmio_mmc_pio.c b/drivers/mmc/host/tmio_mmc_pio.c index abad01b37cfb..fc3eb1a7fe24 100644 --- a/drivers/mmc/host/tmio_mmc_pio.c +++ b/drivers/mmc/host/tmio_mmc_pio.c @@ -39,6 +39,7 @@ #include #include #include +#include #include #include #include @@ -951,6 +952,8 @@ int __devinit tmio_mmc_host_probe(struct tmio_mmc_host **host, mmc_add_host(mmc); + dev_pm_qos_expose_latency_limit(&pdev->dev, 100); + /* Unmask the IRQs we want to know about */ if (!_host->chan_rx) irq_mask |= TMIO_MASK_READOP; @@ -989,6 +992,8 @@ void tmio_mmc_host_remove(struct tmio_mmc_host *host) || host->mmc->caps & MMC_CAP_NONREMOVABLE) pm_runtime_get_sync(&pdev->dev); + dev_pm_qos_hide_latency_limit(&pdev->dev); + mmc_remove_host(host->mmc); cancel_work_sync(&host->done); cancel_delayed_work_sync(&host->delayed_reset_work); -- cgit v1.2.3 From efe6a8ad7fc55b350ce968cae4c757d35e986285 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Tue, 13 Mar 2012 01:02:15 +0100 Subject: sh_mmcif / PM: Use PM QoS latency constraint Make sh_mmcif call dev_pm_qos_expose_latency_limit() to expose the PM QoS latency limit to user space and specify the initial value of it as 100 microseconds. Signed-off-by: Rafael J. Wysocki --- drivers/mmc/host/sh_mmcif.c | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'drivers') diff --git a/drivers/mmc/host/sh_mmcif.c b/drivers/mmc/host/sh_mmcif.c index f5d8b53be333..a4ad93bf8b41 100644 --- a/drivers/mmc/host/sh_mmcif.c +++ b/drivers/mmc/host/sh_mmcif.c @@ -56,6 +56,7 @@ #include #include #include +#include #include #include #include @@ -1347,6 +1348,8 @@ static int __devinit sh_mmcif_probe(struct platform_device *pdev) mmc_detect_change(host->mmc, 0); + dev_pm_qos_expose_latency_limit(&pdev->dev, 100); + dev_info(&pdev->dev, "driver version %s\n", DRIVER_VERSION); dev_dbg(&pdev->dev, "chip ver H'%04x\n", sh_mmcif_readl(host->addr, MMCIF_CE_VERSION) & 0x0000ffff); @@ -1374,6 +1377,8 @@ static int __devexit sh_mmcif_remove(struct platform_device *pdev) host->dying = true; pm_runtime_get_sync(&pdev->dev); + dev_pm_qos_hide_latency_limit(&pdev->dev); + mmc_remove_host(host->mmc); sh_mmcif_writel(host->addr, MMCIF_CE_INT_MASK, MASK_ALL); -- cgit v1.2.3 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') 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; /* -- cgit v1.2.3 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') 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; -- cgit v1.2.3 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') 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. -- cgit v1.2.3 From 2ee619f9487c2acc1efdf2c78e68e2bd51b635fa Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Tue, 13 Mar 2012 22:40:00 +0100 Subject: 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 --- drivers/clocksource/sh_tmu.c | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'drivers') 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; -- cgit v1.2.3 From 615a445f7f8a077c145e737864ae59a4d8717882 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Tue, 13 Mar 2012 22:40:06 +0100 Subject: 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(+) (limited to 'drivers') 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; -- cgit v1.2.3 From 57d13370cfaf6017c68981e66ff5b3bf20a2705c Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Tue, 13 Mar 2012 22:40:14 +0100 Subject: 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(+) (limited to 'drivers') 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; -- cgit v1.2.3 From ab5f299f51259fd2466cf35c89d79bd960e0fc32 Mon Sep 17 00:00:00 2001 From: MyungJoo Ham Date: Fri, 16 Mar 2012 21:54:53 +0100 Subject: PM / devfreq: add relation of recommended frequency. The semantics of "target frequency" given to devfreq driver from devfreq framework has always been interpretted as "at least" or GLB (greatest lower bound). However, the framework might want the device driver to limit its max frequency (LUB: least upper bound), especially if it is given by thermal framework (it's too hot). Thus, the target fuction should have another parameter to express whether the framework wants GLB or LUB. And, the additional parameter, "u32 flags", does it. With the update, devfreq_recommended_opp() is also updated. Signed-off-by: MyungJoo Ham Signed-off-by: Kyungmin Park Reviewed-by: Mike Turquette Signed-off-by: Rafael J. Wysocki --- drivers/devfreq/devfreq.c | 42 ++++++++++++++++++++++++++++++++++++++---- drivers/devfreq/exynos4_bus.c | 14 ++++++++++---- 2 files changed, 48 insertions(+), 8 deletions(-) (limited to 'drivers') diff --git a/drivers/devfreq/devfreq.c b/drivers/devfreq/devfreq.c index a129a7b6bfd1..70c31d43fff3 100644 --- a/drivers/devfreq/devfreq.c +++ b/drivers/devfreq/devfreq.c @@ -83,6 +83,7 @@ int update_devfreq(struct devfreq *devfreq) { unsigned long freq; int err = 0; + u32 flags = 0; if (!mutex_is_locked(&devfreq->lock)) { WARN(true, "devfreq->lock must be locked by the caller.\n"); @@ -94,7 +95,24 @@ int update_devfreq(struct devfreq *devfreq) if (err) return err; - err = devfreq->profile->target(devfreq->dev.parent, &freq); + /* + * Adjust the freuqency with user freq and QoS. + * + * List from the highest proiority + * max_freq (probably called by thermal when it's too hot) + * min_freq + */ + + if (devfreq->min_freq && freq < devfreq->min_freq) { + freq = devfreq->min_freq; + flags &= ~DEVFREQ_FLAG_LEAST_UPPER_BOUND; /* Use GLB */ + } + if (devfreq->max_freq && freq > devfreq->max_freq) { + freq = devfreq->max_freq; + flags |= DEVFREQ_FLAG_LEAST_UPPER_BOUND; /* Use LUB */ + } + + err = devfreq->profile->target(devfreq->dev.parent, &freq, flags); if (err) return err; @@ -625,14 +643,30 @@ module_exit(devfreq_exit); * freq value given to target callback. * @dev The devfreq user device. (parent of devfreq) * @freq The frequency given to target function + * @flags Flags handed from devfreq framework. * */ -struct opp *devfreq_recommended_opp(struct device *dev, unsigned long *freq) +struct opp *devfreq_recommended_opp(struct device *dev, unsigned long *freq, + u32 flags) { - struct opp *opp = opp_find_freq_ceil(dev, freq); + struct opp *opp; - if (opp == ERR_PTR(-ENODEV)) + if (flags & DEVFREQ_FLAG_LEAST_UPPER_BOUND) { + /* The freq is an upper bound. opp should be lower */ opp = opp_find_freq_floor(dev, freq); + + /* If not available, use the closest opp */ + if (opp == ERR_PTR(-ENODEV)) + opp = opp_find_freq_ceil(dev, freq); + } else { + /* The freq is an lower bound. opp should be higher */ + opp = opp_find_freq_ceil(dev, freq); + + /* If not available, use the closest opp */ + if (opp == ERR_PTR(-ENODEV)) + opp = opp_find_freq_floor(dev, freq); + } + return opp; } diff --git a/drivers/devfreq/exynos4_bus.c b/drivers/devfreq/exynos4_bus.c index 590d6865e388..1a361e99965a 100644 --- a/drivers/devfreq/exynos4_bus.c +++ b/drivers/devfreq/exynos4_bus.c @@ -619,13 +619,19 @@ static int exynos4_bus_setvolt(struct busfreq_data *data, struct opp *opp, return err; } -static int exynos4_bus_target(struct device *dev, unsigned long *_freq) +static int exynos4_bus_target(struct device *dev, unsigned long *_freq, + u32 flags) { int err = 0; - struct busfreq_data *data = dev_get_drvdata(dev); - struct opp *opp = devfreq_recommended_opp(dev, _freq); - unsigned long old_freq = opp_get_freq(data->curr_opp); + struct platform_device *pdev = container_of(dev, struct platform_device, + dev); + struct busfreq_data *data = platform_get_drvdata(pdev); + struct opp *opp = devfreq_recommended_opp(dev, _freq, flags); unsigned long freq = opp_get_freq(opp); + unsigned long old_freq = opp_get_freq(data->curr_opp); + + if (IS_ERR(opp)) + return PTR_ERR(opp); if (old_freq == freq) return 0; -- cgit v1.2.3 From 18dd2ece3cde14cfd42e95a89eb14016699a5f15 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Mon, 19 Mar 2012 10:38:14 +0100 Subject: PM / Domains: Check domain status during hibernation restore of devices Power domains that were off before hibernation shouldn't be turned on during device restore, so prevent that from happening. This change fixes up commit 65533bbf63b4f37723fdfedc73d0653958973323 PM / Domains: Fix hibernation restore of devices, v2 that didn't include it by mistake. Signed-off-by: Rafael J. Wysocki --- drivers/base/power/domain.c | 3 +++ 1 file changed, 3 insertions(+) (limited to 'drivers') diff --git a/drivers/base/power/domain.c b/drivers/base/power/domain.c index b6ff6ecf519d..73ce9fbe9839 100644 --- a/drivers/base/power/domain.c +++ b/drivers/base/power/domain.c @@ -1127,6 +1127,9 @@ static int pm_genpd_restore_noirq(struct device *dev) } } + if (genpd->suspend_power_off) + return 0; + pm_genpd_poweron(genpd); return dev_gpd_data(dev)->always_on ? 0 : genpd_start_dev(genpd, dev); -- cgit v1.2.3