From 564b905ab10d17fb42f86aa8b7b9b796276d1336 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Thu, 23 Jun 2011 01:52:55 +0200 Subject: PM / Domains: Rename struct dev_power_domain to struct dev_pm_domain The naming convention used by commit 7538e3db6e015e890825fbd9f86599b (PM: Add support for device power domains), which introduced the struct dev_power_domain type for representing device power domains, evidently confuses some developers who tend to think that objects of this type must correspond to "power domains" as defined by hardware, which is not the case. Namely, at the kernel level, a struct dev_power_domain object can represent arbitrary set of devices that are mutually dependent power management-wise and need not belong to one hardware power domain. To avoid that confusion, rename struct dev_power_domain to struct dev_pm_domain and rename the related pointers in struct device and struct pm_clk_notifier_block from pwr_domain to pm_domain. Signed-off-by: Rafael J. Wysocki Acked-by: Kevin Hilman diff --git a/Documentation/power/devices.txt b/Documentation/power/devices.txt index 64565aa..85c6f98 100644 --- a/Documentation/power/devices.txt +++ b/Documentation/power/devices.txt @@ -506,8 +506,8 @@ routines. Nevertheless, different callback pointers are used in case there is a situation where it actually matters. -Device Power Domains --------------------- +Device Power Management Domains +------------------------------- Sometimes devices share reference clocks or other power resources. In those cases it generally is not possible to put devices into low-power states individually. Instead, a set of devices sharing a power resource can be put @@ -516,8 +516,8 @@ power resource. Of course, they also need to be put into the full-power state together, by turning the shared power resource on. A set of devices with this property is often referred to as a power domain. -Support for power domains is provided through the pwr_domain field of struct -device. This field is a pointer to an object of type struct dev_power_domain, +Support for power domains is provided through the pm_domain field of struct +device. This field is a pointer to an object of type struct dev_pm_domain, defined in include/linux/pm.h, providing a set of power management callbacks analogous to the subsystem-level and device driver callbacks that are executed for the given device during all power transitions, instead of the respective diff --git a/arch/arm/mach-omap1/pm_bus.c b/arch/arm/mach-omap1/pm_bus.c index 334fb88..212f331 100644 --- a/arch/arm/mach-omap1/pm_bus.c +++ b/arch/arm/mach-omap1/pm_bus.c @@ -49,20 +49,20 @@ static int omap1_pm_runtime_resume(struct device *dev) return pm_generic_runtime_resume(dev); } -static struct dev_power_domain default_power_domain = { +static struct dev_pm_domain default_pm_domain = { .ops = { .runtime_suspend = omap1_pm_runtime_suspend, .runtime_resume = omap1_pm_runtime_resume, USE_PLATFORM_PM_SLEEP_OPS }, }; -#define OMAP1_PWR_DOMAIN (&default_power_domain) +#define OMAP1_PM_DOMAIN (&default_pm_domain) #else -#define OMAP1_PWR_DOMAIN NULL +#define OMAP1_PM_DOMAIN NULL #endif /* CONFIG_PM_RUNTIME */ static struct pm_clk_notifier_block platform_bus_notifier = { - .pwr_domain = OMAP1_PWR_DOMAIN, + .pm_domain = OMAP1_PM_DOMAIN, .con_ids = { "ick", "fck", NULL, }, }; diff --git a/arch/arm/mach-shmobile/pm_runtime.c b/arch/arm/mach-shmobile/pm_runtime.c index 2d1b67a..99802d2 100644 --- a/arch/arm/mach-shmobile/pm_runtime.c +++ b/arch/arm/mach-shmobile/pm_runtime.c @@ -28,7 +28,7 @@ static int default_platform_runtime_idle(struct device *dev) return pm_runtime_suspend(dev); } -static struct dev_power_domain default_power_domain = { +static struct dev_pm_domain default_pm_domain = { .ops = { .runtime_suspend = pm_runtime_clk_suspend, .runtime_resume = pm_runtime_clk_resume, @@ -37,16 +37,16 @@ static struct dev_power_domain default_power_domain = { }, }; -#define DEFAULT_PWR_DOMAIN_PTR (&default_power_domain) +#define DEFAULT_PM_DOMAIN_PTR (&default_pm_domain) #else -#define DEFAULT_PWR_DOMAIN_PTR NULL +#define DEFAULT_PM_DOMAIN_PTR NULL #endif /* CONFIG_PM_RUNTIME */ static struct pm_clk_notifier_block platform_bus_notifier = { - .pwr_domain = DEFAULT_PWR_DOMAIN_PTR, + .pm_domain = DEFAULT_PM_DOMAIN_PTR, .con_ids = { NULL, }, }; diff --git a/arch/arm/plat-omap/omap_device.c b/arch/arm/plat-omap/omap_device.c index 49fc0df..d21579b 100644 --- a/arch/arm/plat-omap/omap_device.c +++ b/arch/arm/plat-omap/omap_device.c @@ -564,7 +564,7 @@ static int _od_runtime_resume(struct device *dev) return pm_generic_runtime_resume(dev); } -static struct dev_power_domain omap_device_power_domain = { +static struct dev_pm_domain omap_device_pm_domain = { .ops = { .runtime_suspend = _od_runtime_suspend, .runtime_idle = _od_runtime_idle, @@ -586,7 +586,7 @@ int omap_device_register(struct omap_device *od) pr_debug("omap_device: %s: registering\n", od->pdev.name); od->pdev.dev.parent = &omap_device_parent; - od->pdev.dev.pwr_domain = &omap_device_power_domain; + od->pdev.dev.pm_domain = &omap_device_pm_domain; return platform_device_register(&od->pdev); } diff --git a/arch/sh/kernel/cpu/shmobile/pm_runtime.c b/arch/sh/kernel/cpu/shmobile/pm_runtime.c index 64c807c..bf280c8 100644 --- a/arch/sh/kernel/cpu/shmobile/pm_runtime.c +++ b/arch/sh/kernel/cpu/shmobile/pm_runtime.c @@ -256,7 +256,7 @@ out: return ret; } -static struct dev_power_domain default_power_domain = { +static struct dev_pm_domain default_pm_domain = { .ops = { .runtime_suspend = default_platform_runtime_suspend, .runtime_resume = default_platform_runtime_resume, @@ -285,7 +285,7 @@ static int platform_bus_notify(struct notifier_block *nb, hwblk_disable(hwblk_info, hwblk); /* make sure driver re-inits itself once */ __set_bit(PDEV_ARCHDATA_FLAG_INIT, &pdev->archdata.flags); - dev->pwr_domain = &default_power_domain; + dev->pm_domain = &default_pm_domain; break; /* TODO: add BUS_NOTIFY_BIND_DRIVER and increase idle count */ case BUS_NOTIFY_BOUND_DRIVER: @@ -299,7 +299,7 @@ static int platform_bus_notify(struct notifier_block *nb, __set_bit(PDEV_ARCHDATA_FLAG_INIT, &pdev->archdata.flags); break; case BUS_NOTIFY_DEL_DEVICE: - dev->pwr_domain = NULL; + dev->pm_domain = NULL; break; } return 0; diff --git a/drivers/base/power/clock_ops.c b/drivers/base/power/clock_ops.c index ad367c4..c562481 100644 --- a/drivers/base/power/clock_ops.c +++ b/drivers/base/power/clock_ops.c @@ -278,11 +278,11 @@ int pm_runtime_clk_resume(struct device *dev) * * For this function to work, @nb must be a member of an object of type * struct pm_clk_notifier_block containing all of the requisite data. - * Specifically, the pwr_domain member of that object is copied to the device's - * pwr_domain field and its con_ids member is used to populate the device's list + * Specifically, the pm_domain member of that object is copied to the device's + * pm_domain field and its con_ids member is used to populate the device's list * of runtime PM clocks, depending on @action. * - * If the device's pwr_domain field is already populated with a value different + * If the device's pm_domain field is already populated with a value different * from the one stored in the struct pm_clk_notifier_block object, the function * does nothing. */ @@ -300,14 +300,14 @@ static int pm_runtime_clk_notify(struct notifier_block *nb, switch (action) { case BUS_NOTIFY_ADD_DEVICE: - if (dev->pwr_domain) + if (dev->pm_domain) break; error = pm_runtime_clk_init(dev); if (error) break; - dev->pwr_domain = clknb->pwr_domain; + dev->pm_domain = clknb->pm_domain; if (clknb->con_ids[0]) { for (con_id = clknb->con_ids; *con_id; con_id++) pm_runtime_clk_add(dev, *con_id); @@ -317,10 +317,10 @@ static int pm_runtime_clk_notify(struct notifier_block *nb, break; case BUS_NOTIFY_DEL_DEVICE: - if (dev->pwr_domain != clknb->pwr_domain) + if (dev->pm_domain != clknb->pm_domain) break; - dev->pwr_domain = NULL; + dev->pm_domain = NULL; pm_runtime_clk_destroy(dev); break; } diff --git a/drivers/base/power/main.c b/drivers/base/power/main.c index 06f09bf..85b591a 100644 --- a/drivers/base/power/main.c +++ b/drivers/base/power/main.c @@ -425,9 +425,9 @@ static int device_resume_noirq(struct device *dev, pm_message_t state) TRACE_DEVICE(dev); TRACE_RESUME(0); - if (dev->pwr_domain) { + if (dev->pm_domain) { pm_dev_dbg(dev, state, "EARLY power domain "); - error = pm_noirq_op(dev, &dev->pwr_domain->ops, state); + error = pm_noirq_op(dev, &dev->pm_domain->ops, state); } else if (dev->type && dev->type->pm) { pm_dev_dbg(dev, state, "EARLY type "); error = pm_noirq_op(dev, dev->type->pm, state); @@ -521,9 +521,9 @@ static int device_resume(struct device *dev, pm_message_t state, bool async) if (!dev->power.is_suspended) goto Unlock; - if (dev->pwr_domain) { + if (dev->pm_domain) { pm_dev_dbg(dev, state, "power domain "); - error = pm_op(dev, &dev->pwr_domain->ops, state); + error = pm_op(dev, &dev->pm_domain->ops, state); goto End; } @@ -641,10 +641,10 @@ static void device_complete(struct device *dev, pm_message_t state) { device_lock(dev); - if (dev->pwr_domain) { + if (dev->pm_domain) { pm_dev_dbg(dev, state, "completing power domain "); - if (dev->pwr_domain->ops.complete) - dev->pwr_domain->ops.complete(dev); + if (dev->pm_domain->ops.complete) + dev->pm_domain->ops.complete(dev); } else if (dev->type && dev->type->pm) { pm_dev_dbg(dev, state, "completing type "); if (dev->type->pm->complete) @@ -744,9 +744,9 @@ static int device_suspend_noirq(struct device *dev, pm_message_t state) { int error; - if (dev->pwr_domain) { + if (dev->pm_domain) { pm_dev_dbg(dev, state, "LATE power domain "); - error = pm_noirq_op(dev, &dev->pwr_domain->ops, state); + error = pm_noirq_op(dev, &dev->pm_domain->ops, state); if (error) return error; } else if (dev->type && dev->type->pm) { @@ -853,9 +853,9 @@ static int __device_suspend(struct device *dev, pm_message_t state, bool async) goto Unlock; } - if (dev->pwr_domain) { + if (dev->pm_domain) { pm_dev_dbg(dev, state, "power domain "); - error = pm_op(dev, &dev->pwr_domain->ops, state); + error = pm_op(dev, &dev->pm_domain->ops, state); goto End; } @@ -982,11 +982,11 @@ static int device_prepare(struct device *dev, pm_message_t state) device_lock(dev); - if (dev->pwr_domain) { + if (dev->pm_domain) { pm_dev_dbg(dev, state, "preparing power domain "); - if (dev->pwr_domain->ops.prepare) - error = dev->pwr_domain->ops.prepare(dev); - suspend_report_result(dev->pwr_domain->ops.prepare, error); + if (dev->pm_domain->ops.prepare) + error = dev->pm_domain->ops.prepare(dev); + suspend_report_result(dev->pm_domain->ops.prepare, error); if (error) goto End; } else if (dev->type && dev->type->pm) { diff --git a/drivers/base/power/runtime.c b/drivers/base/power/runtime.c index 0d4587b..5f5c423 100644 --- a/drivers/base/power/runtime.c +++ b/drivers/base/power/runtime.c @@ -213,8 +213,8 @@ static int rpm_idle(struct device *dev, int rpmflags) dev->power.idle_notification = true; - if (dev->pwr_domain) - callback = dev->pwr_domain->ops.runtime_idle; + if (dev->pm_domain) + callback = dev->pm_domain->ops.runtime_idle; else if (dev->type && dev->type->pm) callback = dev->type->pm->runtime_idle; else if (dev->class && dev->class->pm) @@ -374,8 +374,8 @@ static int rpm_suspend(struct device *dev, int rpmflags) __update_runtime_status(dev, RPM_SUSPENDING); - if (dev->pwr_domain) - callback = dev->pwr_domain->ops.runtime_suspend; + if (dev->pm_domain) + callback = dev->pm_domain->ops.runtime_suspend; else if (dev->type && dev->type->pm) callback = dev->type->pm->runtime_suspend; else if (dev->class && dev->class->pm) @@ -573,8 +573,8 @@ static int rpm_resume(struct device *dev, int rpmflags) __update_runtime_status(dev, RPM_RESUMING); - if (dev->pwr_domain) - callback = dev->pwr_domain->ops.runtime_resume; + if (dev->pm_domain) + callback = dev->pm_domain->ops.runtime_resume; else if (dev->type && dev->type->pm) callback = dev->type->pm->runtime_resume; else if (dev->class && dev->class->pm) diff --git a/include/linux/device.h b/include/linux/device.h index e4f62d8..160d4dd 100644 --- a/include/linux/device.h +++ b/include/linux/device.h @@ -516,7 +516,7 @@ struct device_dma_parameters { * minimizes board-specific #ifdefs in drivers. * @power: For device power management. * See Documentation/power/devices.txt for details. - * @pwr_domain: Provide callbacks that are executed during system suspend, + * @pm_domain: Provide callbacks that are executed during system suspend, * hibernation, system resume and during runtime PM transitions * along with subsystem-level and driver-level callbacks. * @numa_node: NUMA node this device is close to. @@ -567,7 +567,7 @@ struct device { void *platform_data; /* Platform specific data, device core doesn't touch it */ struct dev_pm_info power; - struct dev_power_domain *pwr_domain; + struct dev_pm_domain *pm_domain; #ifdef CONFIG_NUMA int numa_node; /* NUMA node this device is close to */ diff --git a/include/linux/pm.h b/include/linux/pm.h index 411e4f4..e396320 100644 --- a/include/linux/pm.h +++ b/include/linux/pm.h @@ -472,7 +472,7 @@ extern void update_pm_runtime_accounting(struct device *dev); * hibernation, system resume and during runtime PM transitions along with * subsystem-level and driver-level callbacks. */ -struct dev_power_domain { +struct dev_pm_domain { struct dev_pm_ops ops; }; diff --git a/include/linux/pm_runtime.h b/include/linux/pm_runtime.h index 878cf84..ef91904 100644 --- a/include/linux/pm_runtime.h +++ b/include/linux/pm_runtime.h @@ -247,7 +247,7 @@ static inline void pm_runtime_dont_use_autosuspend(struct device *dev) struct pm_clk_notifier_block { struct notifier_block nb; - struct dev_power_domain *pwr_domain; + struct dev_pm_domain *pm_domain; char *con_ids[]; }; -- cgit v0.10.2 From dc6e4e56e6ef473a696a1ab24f80b79b9aceb92d Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Thu, 23 Jun 2011 01:53:04 +0200 Subject: PM: subsys_data in struct dev_pm_info need not depend on RM_RUNTIME The subsys_data field of struct dev_pm_info, introduced by commit 1d2b71f61b6a10216274e27b717becf9ae101fc7 (PM / Runtime: Add subsystem data field to struct dev_pm_info), is going to be used even if CONFIG_PM_RUNTIME is not set, so move it from under the #ifdef. Signed-off-by: Rafael J. Wysocki diff --git a/include/linux/pm.h b/include/linux/pm.h index e396320..7e8f0763 100644 --- a/include/linux/pm.h +++ b/include/linux/pm.h @@ -461,8 +461,8 @@ struct dev_pm_info { unsigned long active_jiffies; unsigned long suspended_jiffies; unsigned long accounting_timestamp; - void *subsys_data; /* Owned by the subsystem. */ #endif + void *subsys_data; /* Owned by the subsystem. */ }; extern void update_pm_runtime_accounting(struct device *dev); -- cgit v0.10.2 From f721889ff65afa6243c463832c74dee3bed418d5 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Fri, 1 Jul 2011 22:12:45 +0200 Subject: PM / Domains: Support for generic I/O PM domains (v8) Introduce common headers, helper functions and callbacks allowing platforms to use simple generic power domains for runtime power management. Introduce struct generic_pm_domain to be used for representing power domains that each contain a number of devices and may be parent domains or subdomains with respect to other power domains. Among other things, this structure includes callbacks to be provided by platforms for performing specific tasks related to power management (i.e. ->stop_device() may disable a device's clocks, while ->start_device() may enable them, ->power_off() is supposed to remove power from the entire power domain and ->power_on() is supposed to restore it). Introduce functions that can be used as power domain runtime PM callbacks, pm_genpd_runtime_suspend() and pm_genpd_runtime_resume(), as well as helper functions for the initialization of a power domain represented by a struct generic_power_domain object, adding a device to or removing a device from it and adding or removing subdomains. Introduce configuration option CONFIG_PM_GENERIC_DOMAINS to be selected by the platforms that want to use the new code. Signed-off-by: Rafael J. Wysocki Acked-by: Greg Kroah-Hartman Reviewed-by: Kevin Hilman diff --git a/drivers/base/power/Makefile b/drivers/base/power/Makefile index 3647e11..2639ae7 100644 --- a/drivers/base/power/Makefile +++ b/drivers/base/power/Makefile @@ -3,6 +3,7 @@ obj-$(CONFIG_PM_SLEEP) += main.o wakeup.o obj-$(CONFIG_PM_RUNTIME) += runtime.o obj-$(CONFIG_PM_TRACE_RTC) += trace.o obj-$(CONFIG_PM_OPP) += opp.o +obj-$(CONFIG_PM_GENERIC_DOMAINS) += domain.o obj-$(CONFIG_HAVE_CLK) += clock_ops.o ccflags-$(CONFIG_DEBUG_DRIVER) := -DDEBUG \ No newline at end of file diff --git a/drivers/base/power/domain.c b/drivers/base/power/domain.c new file mode 100644 index 0000000..fd31be3 --- /dev/null +++ b/drivers/base/power/domain.c @@ -0,0 +1,494 @@ +/* + * drivers/base/power/domain.c - Common code related to device power domains. + * + * Copyright (C) 2011 Rafael J. Wysocki , Renesas Electronics Corp. + * + * This file is released under the GPLv2. + */ + +#include +#include +#include +#include +#include +#include +#include + +#ifdef CONFIG_PM_RUNTIME + +static void genpd_sd_counter_dec(struct generic_pm_domain *genpd) +{ + if (!WARN_ON(genpd->sd_count == 0)) + genpd->sd_count--; +} + +/** + * __pm_genpd_save_device - Save the pre-suspend state of a device. + * @dle: Device list entry of the device to save the state of. + * @genpd: PM domain the device belongs to. + */ +static int __pm_genpd_save_device(struct dev_list_entry *dle, + struct generic_pm_domain *genpd) +{ + struct device *dev = dle->dev; + struct device_driver *drv = dev->driver; + int ret = 0; + + if (dle->need_restore) + return 0; + + if (drv && drv->pm && drv->pm->runtime_suspend) { + if (genpd->start_device) + genpd->start_device(dev); + + ret = drv->pm->runtime_suspend(dev); + + if (genpd->stop_device) + genpd->stop_device(dev); + } + + if (!ret) + dle->need_restore = true; + + return ret; +} + +/** + * __pm_genpd_restore_device - Restore the pre-suspend state of a device. + * @dle: Device list entry of the device to restore the state of. + * @genpd: PM domain the device belongs to. + */ +static void __pm_genpd_restore_device(struct dev_list_entry *dle, + struct generic_pm_domain *genpd) +{ + struct device *dev = dle->dev; + struct device_driver *drv = dev->driver; + + if (!dle->need_restore) + return; + + if (drv && drv->pm && drv->pm->runtime_resume) { + if (genpd->start_device) + genpd->start_device(dev); + + drv->pm->runtime_resume(dev); + + if (genpd->stop_device) + genpd->stop_device(dev); + } + + dle->need_restore = false; +} + +/** + * pm_genpd_poweroff - Remove power from a given PM domain. + * @genpd: PM domain to power down. + * + * If all of the @genpd's devices have been suspended and all of its subdomains + * have been powered down, run the runtime suspend callbacks provided by all of + * the @genpd's devices' drivers and remove power from @genpd. + */ +static int pm_genpd_poweroff(struct generic_pm_domain *genpd) +{ + struct generic_pm_domain *parent; + struct dev_list_entry *dle; + unsigned int not_suspended; + int ret; + + if (genpd->power_is_off) + return 0; + + if (genpd->sd_count > 0) + return -EBUSY; + + not_suspended = 0; + list_for_each_entry(dle, &genpd->dev_list, node) + if (dle->dev->driver && !pm_runtime_suspended(dle->dev)) + not_suspended++; + + if (not_suspended > genpd->in_progress) + return -EBUSY; + + if (genpd->gov && genpd->gov->power_down_ok) { + if (!genpd->gov->power_down_ok(&genpd->domain)) + return -EAGAIN; + } + + list_for_each_entry_reverse(dle, &genpd->dev_list, node) { + ret = __pm_genpd_save_device(dle, genpd); + if (ret) + goto err_dev; + } + + if (genpd->power_off) + genpd->power_off(genpd); + + genpd->power_is_off = true; + + parent = genpd->parent; + if (parent) { + genpd_sd_counter_dec(parent); + if (parent->sd_count == 0) + queue_work(pm_wq, &parent->power_off_work); + } + + return 0; + + err_dev: + list_for_each_entry_continue(dle, &genpd->dev_list, node) + __pm_genpd_restore_device(dle, genpd); + + return ret; +} + +/** + * genpd_power_off_work_fn - Power off PM domain whose subdomain count is 0. + * @work: Work structure used for scheduling the execution of this function. + */ +static void genpd_power_off_work_fn(struct work_struct *work) +{ + struct generic_pm_domain *genpd; + + genpd = container_of(work, struct generic_pm_domain, power_off_work); + + if (genpd->parent) + mutex_lock(&genpd->parent->lock); + mutex_lock_nested(&genpd->lock, SINGLE_DEPTH_NESTING); + pm_genpd_poweroff(genpd); + mutex_unlock(&genpd->lock); + if (genpd->parent) + mutex_unlock(&genpd->parent->lock); +} + +/** + * pm_genpd_runtime_suspend - Suspend a device belonging to I/O PM domain. + * @dev: Device to suspend. + * + * Carry out a runtime 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_runtime_suspend(struct device *dev) +{ + struct generic_pm_domain *genpd; + + dev_dbg(dev, "%s()\n", __func__); + + if (IS_ERR_OR_NULL(dev->pm_domain)) + return -EINVAL; + + genpd = container_of(dev->pm_domain, struct generic_pm_domain, domain); + + if (genpd->parent) + mutex_lock(&genpd->parent->lock); + mutex_lock_nested(&genpd->lock, SINGLE_DEPTH_NESTING); + + if (genpd->stop_device) { + int ret = genpd->stop_device(dev); + if (ret) + goto out; + } + genpd->in_progress++; + pm_genpd_poweroff(genpd); + genpd->in_progress--; + + out: + mutex_unlock(&genpd->lock); + if (genpd->parent) + mutex_unlock(&genpd->parent->lock); + + return 0; +} + +/** + * pm_genpd_poweron - Restore power to a given PM domain and its parents. + * @genpd: PM domain to power up. + * + * Restore power to @genpd and all of its parents so that it is possible to + * resume a device belonging to it. + */ +static int pm_genpd_poweron(struct generic_pm_domain *genpd) +{ + int ret = 0; + + start: + if (genpd->parent) + mutex_lock(&genpd->parent->lock); + mutex_lock_nested(&genpd->lock, SINGLE_DEPTH_NESTING); + + if (!genpd->power_is_off) + goto out; + + if (genpd->parent && genpd->parent->power_is_off) { + mutex_unlock(&genpd->lock); + mutex_unlock(&genpd->parent->lock); + + ret = pm_genpd_poweron(genpd->parent); + if (ret) + return ret; + + goto start; + } + + if (genpd->power_on) { + int ret = genpd->power_on(genpd); + if (ret) + goto out; + } + + genpd->power_is_off = false; + if (genpd->parent) + genpd->parent->sd_count++; + + out: + mutex_unlock(&genpd->lock); + if (genpd->parent) + mutex_unlock(&genpd->parent->lock); + + return ret; +} + +/** + * pm_genpd_runtime_resume - Resume a device belonging to I/O PM domain. + * @dev: Device to resume. + * + * Carry out a runtime 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 PM domain consisting of I/O devices. + */ +static int pm_genpd_runtime_resume(struct device *dev) +{ + struct generic_pm_domain *genpd; + struct dev_list_entry *dle; + int ret; + + dev_dbg(dev, "%s()\n", __func__); + + if (IS_ERR_OR_NULL(dev->pm_domain)) + return -EINVAL; + + genpd = container_of(dev->pm_domain, struct generic_pm_domain, domain); + + ret = pm_genpd_poweron(genpd); + if (ret) + return ret; + + mutex_lock(&genpd->lock); + + list_for_each_entry(dle, &genpd->dev_list, node) { + if (dle->dev == dev) { + __pm_genpd_restore_device(dle, genpd); + break; + } + } + + if (genpd->start_device) + genpd->start_device(dev); + + mutex_unlock(&genpd->lock); + + return 0; +} + +#else + +static inline void genpd_power_off_work_fn(struct work_struct *work) {} + +#define pm_genpd_runtime_suspend NULL +#define pm_genpd_runtime_resume NULL + +#endif /* CONFIG_PM_RUNTIME */ + +/** + * pm_genpd_add_device - Add a device to an I/O PM domain. + * @genpd: PM domain to add the device to. + * @dev: Device to be added. + */ +int pm_genpd_add_device(struct generic_pm_domain *genpd, struct device *dev) +{ + struct dev_list_entry *dle; + int ret = 0; + + dev_dbg(dev, "%s()\n", __func__); + + if (IS_ERR_OR_NULL(genpd) || IS_ERR_OR_NULL(dev)) + return -EINVAL; + + mutex_lock(&genpd->lock); + + if (genpd->power_is_off) { + ret = -EINVAL; + goto out; + } + + list_for_each_entry(dle, &genpd->dev_list, node) + if (dle->dev == dev) { + ret = -EINVAL; + goto out; + } + + dle = kzalloc(sizeof(*dle), GFP_KERNEL); + if (!dle) { + ret = -ENOMEM; + goto out; + } + + dle->dev = dev; + dle->need_restore = false; + list_add_tail(&dle->node, &genpd->dev_list); + + spin_lock_irq(&dev->power.lock); + dev->pm_domain = &genpd->domain; + spin_unlock_irq(&dev->power.lock); + + out: + mutex_unlock(&genpd->lock); + + return ret; +} + +/** + * pm_genpd_remove_device - Remove a device from an I/O PM domain. + * @genpd: PM domain to remove the device from. + * @dev: Device to be removed. + */ +int pm_genpd_remove_device(struct generic_pm_domain *genpd, + struct device *dev) +{ + struct dev_list_entry *dle; + int ret = -EINVAL; + + dev_dbg(dev, "%s()\n", __func__); + + if (IS_ERR_OR_NULL(genpd) || IS_ERR_OR_NULL(dev)) + return -EINVAL; + + mutex_lock(&genpd->lock); + + list_for_each_entry(dle, &genpd->dev_list, node) { + if (dle->dev != dev) + continue; + + spin_lock_irq(&dev->power.lock); + dev->pm_domain = NULL; + spin_unlock_irq(&dev->power.lock); + + list_del(&dle->node); + kfree(dle); + + ret = 0; + break; + } + + mutex_unlock(&genpd->lock); + + return ret; +} + +/** + * pm_genpd_add_subdomain - Add a subdomain to an I/O PM domain. + * @genpd: Master PM domain to add the subdomain to. + * @new_subdomain: Subdomain to be added. + */ +int pm_genpd_add_subdomain(struct generic_pm_domain *genpd, + struct generic_pm_domain *new_subdomain) +{ + struct generic_pm_domain *subdomain; + int ret = 0; + + if (IS_ERR_OR_NULL(genpd) || IS_ERR_OR_NULL(new_subdomain)) + return -EINVAL; + + mutex_lock(&genpd->lock); + + if (genpd->power_is_off && !new_subdomain->power_is_off) { + ret = -EINVAL; + goto out; + } + + list_for_each_entry(subdomain, &genpd->sd_list, sd_node) { + if (subdomain == new_subdomain) { + ret = -EINVAL; + goto out; + } + } + + mutex_lock_nested(&new_subdomain->lock, SINGLE_DEPTH_NESTING); + + list_add_tail(&new_subdomain->sd_node, &genpd->sd_list); + new_subdomain->parent = genpd; + if (!subdomain->power_is_off) + genpd->sd_count++; + + mutex_unlock(&new_subdomain->lock); + + out: + mutex_unlock(&genpd->lock); + + return ret; +} + +/** + * pm_genpd_remove_subdomain - Remove a subdomain from an I/O PM domain. + * @genpd: Master PM domain to remove the subdomain from. + * @target: Subdomain to be removed. + */ +int pm_genpd_remove_subdomain(struct generic_pm_domain *genpd, + struct generic_pm_domain *target) +{ + struct generic_pm_domain *subdomain; + int ret = -EINVAL; + + if (IS_ERR_OR_NULL(genpd) || IS_ERR_OR_NULL(target)) + return -EINVAL; + + mutex_lock(&genpd->lock); + + list_for_each_entry(subdomain, &genpd->sd_list, sd_node) { + if (subdomain != target) + continue; + + mutex_lock_nested(&subdomain->lock, SINGLE_DEPTH_NESTING); + + list_del(&subdomain->sd_node); + subdomain->parent = NULL; + if (!subdomain->power_is_off) + genpd_sd_counter_dec(genpd); + + mutex_unlock(&subdomain->lock); + + ret = 0; + break; + } + + mutex_unlock(&genpd->lock); + + return ret; +} + +/** + * pm_genpd_init - Initialize a generic I/O PM domain object. + * @genpd: PM domain object to initialize. + * @gov: PM domain governor to associate with the domain (may be NULL). + * @is_off: Initial value of the domain's power_is_off field. + */ +void pm_genpd_init(struct generic_pm_domain *genpd, + struct dev_power_governor *gov, bool is_off) +{ + if (IS_ERR_OR_NULL(genpd)) + return; + + INIT_LIST_HEAD(&genpd->sd_node); + genpd->parent = NULL; + INIT_LIST_HEAD(&genpd->dev_list); + INIT_LIST_HEAD(&genpd->sd_list); + mutex_init(&genpd->lock); + genpd->gov = gov; + INIT_WORK(&genpd->power_off_work, genpd_power_off_work_fn); + genpd->in_progress = 0; + genpd->sd_count = 0; + genpd->power_is_off = is_off; + genpd->domain.ops.runtime_suspend = pm_genpd_runtime_suspend; + genpd->domain.ops.runtime_resume = pm_genpd_runtime_resume; + genpd->domain.ops.runtime_idle = pm_generic_runtime_idle; +} diff --git a/include/linux/pm_domain.h b/include/linux/pm_domain.h new file mode 100644 index 0000000..b1a22c6 --- /dev/null +++ b/include/linux/pm_domain.h @@ -0,0 +1,78 @@ +/* + * pm_domain.h - Definitions and headers related to device power domains. + * + * Copyright (C) 2011 Rafael J. Wysocki , Renesas Electronics Corp. + * + * This file is released under the GPLv2. + */ + +#ifndef _LINUX_PM_DOMAIN_H +#define _LINUX_PM_DOMAIN_H + +#include + +struct dev_power_governor { + bool (*power_down_ok)(struct dev_pm_domain *domain); +}; + +struct generic_pm_domain { + struct dev_pm_domain domain; /* PM domain operations */ + struct list_head sd_node; /* Node in the parent's subdomain list */ + struct generic_pm_domain *parent; /* Parent PM domain */ + struct list_head sd_list; /* List of dubdomains */ + struct list_head dev_list; /* List of devices */ + struct mutex lock; + struct dev_power_governor *gov; + struct work_struct power_off_work; + unsigned int in_progress; /* Number of devices being suspended now */ + unsigned int sd_count; /* Number of subdomains with power "on" */ + bool power_is_off; /* Whether or not power has been removed */ + int (*power_off)(struct generic_pm_domain *domain); + int (*power_on)(struct generic_pm_domain *domain); + int (*start_device)(struct device *dev); + int (*stop_device)(struct device *dev); +}; + +struct dev_list_entry { + struct list_head node; + struct device *dev; + bool need_restore; +}; + +#ifdef CONFIG_PM_GENERIC_DOMAINS +extern int pm_genpd_add_device(struct generic_pm_domain *genpd, + struct device *dev); +extern int pm_genpd_remove_device(struct generic_pm_domain *genpd, + struct device *dev); +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, + struct generic_pm_domain *target); +extern void pm_genpd_init(struct generic_pm_domain *genpd, + struct dev_power_governor *gov, bool is_off); +#else +static inline int pm_genpd_add_device(struct generic_pm_domain *genpd, + struct device *dev) +{ + return -ENOSYS; +} +static inline int pm_genpd_remove_device(struct generic_pm_domain *genpd, + struct device *dev) +{ + return -ENOSYS; +} +static inline int pm_genpd_add_subdomain(struct generic_pm_domain *genpd, + struct generic_pm_domain *new_sd) +{ + return -ENOSYS; +} +static inline int pm_genpd_remove_subdomain(struct generic_pm_domain *genpd, + struct generic_pm_domain *target) +{ + return -ENOSYS; +} +static inline void pm_genpd_init(struct generic_pm_domain *genpd, + struct dev_power_governor *gov, bool is_off) {} +#endif + +#endif /* _LINUX_PM_DOMAIN_H */ diff --git a/kernel/power/Kconfig b/kernel/power/Kconfig index 87f4d24..e83ac25 100644 --- a/kernel/power/Kconfig +++ b/kernel/power/Kconfig @@ -227,3 +227,7 @@ config PM_OPP config PM_RUNTIME_CLK def_bool y depends on PM_RUNTIME && HAVE_CLK + +config PM_GENERIC_DOMAINS + bool + depends on PM -- cgit v0.10.2 From e5291928839877f8e73c2643ee1d3fe0bcdcaf5c Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Fri, 1 Jul 2011 22:12:59 +0200 Subject: PM: Introduce generic "noirq" callback routines for subsystems (v2) Introduce generic "noirq" power management callback routines for subsystems in addition to the "regular" generic PM callback routines. The new routines will be used, among other things, for implementing system-wide PM transitions support for generic PM domains. Signed-off-by: Rafael J. Wysocki diff --git a/Documentation/power/runtime_pm.txt b/Documentation/power/runtime_pm.txt index b24875b..4b011b1 100644 --- a/Documentation/power/runtime_pm.txt +++ b/Documentation/power/runtime_pm.txt @@ -606,32 +606,60 @@ driver/base/power/generic_ops.c: callback provided by its driver and return its result, or return 0 if not defined + int pm_generic_suspend_noirq(struct device *dev); + - if pm_runtime_suspended(dev) returns "false", invoke the ->suspend_noirq() + callback provided by the device's driver and return its result, or return + 0 if not defined + int pm_generic_resume(struct device *dev); - invoke the ->resume() callback provided by the driver of this device and, if successful, change the device's runtime PM status to 'active' + int pm_generic_resume_noirq(struct device *dev); + - invoke the ->resume_noirq() callback provided by the driver of this device + int pm_generic_freeze(struct device *dev); - if the device has not been suspended at run time, invoke the ->freeze() callback provided by its driver and return its result, or return 0 if not defined + int pm_generic_freeze_noirq(struct device *dev); + - if pm_runtime_suspended(dev) returns "false", invoke the ->freeze_noirq() + callback provided by the device's driver and return its result, or return + 0 if not defined + int pm_generic_thaw(struct device *dev); - if the device has not been suspended at run time, invoke the ->thaw() callback provided by its driver and return its result, or return 0 if not defined + int pm_generic_thaw_noirq(struct device *dev); + - if pm_runtime_suspended(dev) returns "false", invoke the ->thaw_noirq() + callback provided by the device's driver and return its result, or return + 0 if not defined + int pm_generic_poweroff(struct device *dev); - if the device has not been suspended at run time, invoke the ->poweroff() callback provided by its driver and return its result, or return 0 if not defined + int pm_generic_poweroff_noirq(struct device *dev); + - if pm_runtime_suspended(dev) returns "false", run the ->poweroff_noirq() + callback provided by the device's driver and return its result, or return + 0 if not defined + int pm_generic_restore(struct device *dev); - invoke the ->restore() callback provided by the driver of this device and, if successful, change the device's runtime PM status to 'active' + int pm_generic_restore_noirq(struct device *dev); + - invoke the ->restore_noirq() callback provided by the device's driver + These functions can be assigned to the ->runtime_idle(), ->runtime_suspend(), -->runtime_resume(), ->suspend(), ->resume(), ->freeze(), ->thaw(), ->poweroff(), -or ->restore() callback pointers in the subsystem-level dev_pm_ops structures. +->runtime_resume(), ->suspend(), ->suspend_noirq(), ->resume(), +->resume_noirq(), ->freeze(), ->freeze_noirq(), ->thaw(), ->thaw_noirq(), +->poweroff(), ->poweroff_noirq(), ->restore(), ->restore_noirq() callback +pointers in the subsystem-level dev_pm_ops structures. If a subsystem wishes to use all of them at the same time, it can simply assign the GENERIC_SUBSYS_PM_OPS macro, defined in include/linux/pm.h, to its diff --git a/drivers/base/power/generic_ops.c b/drivers/base/power/generic_ops.c index cb3bb36..9508df7 100644 --- a/drivers/base/power/generic_ops.c +++ b/drivers/base/power/generic_ops.c @@ -94,12 +94,13 @@ 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. * * If the device has not been suspended at run time, execute the * suspend/freeze/poweroff/thaw callback provided by its driver, if defined, and * return its error code. Otherwise, return zero. */ -static int __pm_generic_call(struct device *dev, int event) +static int __pm_generic_call(struct device *dev, int event, bool noirq) { const struct dev_pm_ops *pm = dev->driver ? dev->driver->pm : NULL; int (*callback)(struct device *); @@ -109,16 +110,16 @@ static int __pm_generic_call(struct device *dev, int event) switch (event) { case PM_EVENT_SUSPEND: - callback = pm->suspend; + callback = noirq ? pm->suspend_noirq : pm->suspend; break; case PM_EVENT_FREEZE: - callback = pm->freeze; + callback = noirq ? pm->freeze_noirq : pm->freeze; break; case PM_EVENT_HIBERNATE: - callback = pm->poweroff; + callback = noirq ? pm->poweroff_noirq : pm->poweroff; break; case PM_EVENT_THAW: - callback = pm->thaw; + callback = noirq ? pm->thaw_noirq : pm->thaw; break; default: callback = NULL; @@ -129,42 +130,82 @@ static int __pm_generic_call(struct device *dev, int event) } /** + * pm_generic_suspend_noirq - Generic suspend_noirq callback for subsystems. + * @dev: Device to suspend. + */ +int pm_generic_suspend_noirq(struct device *dev) +{ + return __pm_generic_call(dev, PM_EVENT_SUSPEND, true); +} +EXPORT_SYMBOL_GPL(pm_generic_suspend_noirq); + +/** * pm_generic_suspend - Generic suspend callback for subsystems. * @dev: Device to suspend. */ int pm_generic_suspend(struct device *dev) { - return __pm_generic_call(dev, PM_EVENT_SUSPEND); + return __pm_generic_call(dev, PM_EVENT_SUSPEND, false); } EXPORT_SYMBOL_GPL(pm_generic_suspend); /** + * pm_generic_freeze_noirq - Generic freeze_noirq callback for subsystems. + * @dev: Device to freeze. + */ +int pm_generic_freeze_noirq(struct device *dev) +{ + return __pm_generic_call(dev, PM_EVENT_FREEZE, true); +} +EXPORT_SYMBOL_GPL(pm_generic_freeze_noirq); + +/** * 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); + return __pm_generic_call(dev, PM_EVENT_FREEZE, false); } EXPORT_SYMBOL_GPL(pm_generic_freeze); /** + * pm_generic_poweroff_noirq - Generic poweroff_noirq callback for subsystems. + * @dev: Device to handle. + */ +int pm_generic_poweroff_noirq(struct device *dev) +{ + return __pm_generic_call(dev, PM_EVENT_HIBERNATE, true); +} +EXPORT_SYMBOL_GPL(pm_generic_poweroff_noirq); + +/** * 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); + return __pm_generic_call(dev, PM_EVENT_HIBERNATE, false); } EXPORT_SYMBOL_GPL(pm_generic_poweroff); /** + * pm_generic_thaw_noirq - Generic thaw_noirq callback for subsystems. + * @dev: Device to thaw. + */ +int pm_generic_thaw_noirq(struct device *dev) +{ + return __pm_generic_call(dev, PM_EVENT_THAW, true); +} +EXPORT_SYMBOL_GPL(pm_generic_thaw_noirq); + +/** * 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); + return __pm_generic_call(dev, PM_EVENT_THAW, false); } EXPORT_SYMBOL_GPL(pm_generic_thaw); @@ -172,12 +213,13 @@ EXPORT_SYMBOL_GPL(pm_generic_thaw); * __pm_generic_resume - Generic resume/restore callback for subsystems. * @dev: Device to handle. * @event: PM transition of the system under way. + * @bool: Whether or not this is the "noirq" stage. * * Execute the resume/resotre callback provided by the @dev's driver, if * defined. If it returns 0, change the device's runtime PM status to 'active'. * Return the callback's error code. */ -static int __pm_generic_resume(struct device *dev, int event) +static int __pm_generic_resume(struct device *dev, int event, bool noirq) { const struct dev_pm_ops *pm = dev->driver ? dev->driver->pm : NULL; int (*callback)(struct device *); @@ -188,10 +230,10 @@ static int __pm_generic_resume(struct device *dev, int event) switch (event) { case PM_EVENT_RESUME: - callback = pm->resume; + callback = noirq ? pm->resume_noirq : pm->resume; break; case PM_EVENT_RESTORE: - callback = pm->restore; + callback = noirq ? pm->restore_noirq : pm->restore; break; default: callback = NULL; @@ -202,7 +244,7 @@ static int __pm_generic_resume(struct device *dev, int event) return 0; ret = callback(dev); - if (!ret && pm_runtime_enabled(dev)) { + if (!ret && !noirq && pm_runtime_enabled(dev)) { pm_runtime_disable(dev); pm_runtime_set_active(dev); pm_runtime_enable(dev); @@ -212,22 +254,42 @@ static int __pm_generic_resume(struct device *dev, int event) } /** + * pm_generic_resume_noirq - Generic resume_noirq callback for subsystems. + * @dev: Device to resume. + */ +int pm_generic_resume_noirq(struct device *dev) +{ + return __pm_generic_resume(dev, PM_EVENT_RESUME, true); +} +EXPORT_SYMBOL_GPL(pm_generic_resume_noirq); + +/** * pm_generic_resume - Generic resume callback for subsystems. * @dev: Device to resume. */ int pm_generic_resume(struct device *dev) { - return __pm_generic_resume(dev, PM_EVENT_RESUME); + return __pm_generic_resume(dev, PM_EVENT_RESUME, false); } EXPORT_SYMBOL_GPL(pm_generic_resume); /** + * pm_generic_restore_noirq - Generic restore_noirq callback for subsystems. + * @dev: Device to restore. + */ +int pm_generic_restore_noirq(struct device *dev) +{ + return __pm_generic_resume(dev, PM_EVENT_RESTORE, true); +} +EXPORT_SYMBOL_GPL(pm_generic_restore_noirq); + +/** * pm_generic_restore - Generic restore callback for subsystems. * @dev: Device to restore. */ int pm_generic_restore(struct device *dev) { - return __pm_generic_resume(dev, PM_EVENT_RESTORE); + return __pm_generic_resume(dev, PM_EVENT_RESTORE, false); } EXPORT_SYMBOL_GPL(pm_generic_restore); @@ -256,11 +318,17 @@ struct dev_pm_ops generic_subsys_pm_ops = { #ifdef CONFIG_PM_SLEEP .prepare = pm_generic_prepare, .suspend = pm_generic_suspend, + .suspend_noirq = pm_generic_suspend_noirq, .resume = pm_generic_resume, + .resume_noirq = pm_generic_resume_noirq, .freeze = pm_generic_freeze, + .freeze_noirq = pm_generic_freeze_noirq, .thaw = pm_generic_thaw, + .thaw_noirq = pm_generic_thaw_noirq, .poweroff = pm_generic_poweroff, + .poweroff_noirq = pm_generic_poweroff_noirq, .restore = pm_generic_restore, + .restore_noirq = pm_generic_restore_noirq, .complete = pm_generic_complete, #endif #ifdef CONFIG_PM_RUNTIME diff --git a/include/linux/pm.h b/include/linux/pm.h index 7e8f0763..f7c84c9 100644 --- a/include/linux/pm.h +++ b/include/linux/pm.h @@ -553,11 +553,17 @@ extern void __suspend_report_result(const char *function, void *fn, int ret); extern int device_pm_wait_for_dev(struct device *sub, struct device *dev); extern int pm_generic_prepare(struct device *dev); +extern int pm_generic_suspend_noirq(struct device *dev); extern int pm_generic_suspend(struct device *dev); +extern int pm_generic_resume_noirq(struct device *dev); extern int pm_generic_resume(struct device *dev); +extern int pm_generic_freeze_noirq(struct device *dev); extern int pm_generic_freeze(struct device *dev); +extern int pm_generic_thaw_noirq(struct device *dev); extern int pm_generic_thaw(struct device *dev); +extern int pm_generic_restore_noirq(struct device *dev); extern int pm_generic_restore(struct device *dev); +extern int pm_generic_poweroff_noirq(struct device *dev); extern int pm_generic_poweroff(struct device *dev); extern void pm_generic_complete(struct device *dev); -- cgit v0.10.2 From 5248051b9afb6684cd817b2fbdaefa5063761dab Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Fri, 1 Jul 2011 22:13:10 +0200 Subject: PM / Domains: Move code from under #ifdef CONFIG_PM_RUNTIME (v2) There is some code in drivers/base/power/domain.c that will be useful for both runtime PM and system-wide power transitions, so make it depend on CONFIG_PM instead of CONFIG_PM_RUNTIME. Signed-off-by: Rafael J. Wysocki Reviewed-by: Kevin Hilman diff --git a/drivers/base/power/domain.c b/drivers/base/power/domain.c index fd31be3..f14ba32 100644 --- a/drivers/base/power/domain.c +++ b/drivers/base/power/domain.c @@ -14,7 +14,15 @@ #include #include -#ifdef CONFIG_PM_RUNTIME +#ifdef CONFIG_PM + +static struct generic_pm_domain *dev_to_genpd(struct device *dev) +{ + if (IS_ERR_OR_NULL(dev->pm_domain)) + return ERR_PTR(-EINVAL); + + return container_of(dev->pm_domain, struct generic_pm_domain, domain); +} static void genpd_sd_counter_dec(struct generic_pm_domain *genpd) { @@ -23,6 +31,58 @@ static void genpd_sd_counter_dec(struct generic_pm_domain *genpd) } /** + * pm_genpd_poweron - Restore power to a given PM domain and its parents. + * @genpd: PM domain to power up. + * + * Restore power to @genpd and all of its parents so that it is possible to + * resume a device belonging to it. + */ +static int pm_genpd_poweron(struct generic_pm_domain *genpd) +{ + int ret = 0; + + start: + if (genpd->parent) + mutex_lock(&genpd->parent->lock); + mutex_lock_nested(&genpd->lock, SINGLE_DEPTH_NESTING); + + if (!genpd->power_is_off) + goto out; + + if (genpd->parent && genpd->parent->power_is_off) { + mutex_unlock(&genpd->lock); + mutex_unlock(&genpd->parent->lock); + + ret = pm_genpd_poweron(genpd->parent); + if (ret) + return ret; + + goto start; + } + + if (genpd->power_on) { + int ret = genpd->power_on(genpd); + if (ret) + goto out; + } + + genpd->power_is_off = false; + if (genpd->parent) + genpd->parent->sd_count++; + + out: + mutex_unlock(&genpd->lock); + if (genpd->parent) + mutex_unlock(&genpd->parent->lock); + + return ret; +} + +#endif /* CONFIG_PM */ + +#ifdef CONFIG_PM_RUNTIME + +/** * __pm_genpd_save_device - Save the pre-suspend state of a device. * @dle: Device list entry of the device to save the state of. * @genpd: PM domain the device belongs to. @@ -174,11 +234,10 @@ static int pm_genpd_runtime_suspend(struct device *dev) dev_dbg(dev, "%s()\n", __func__); - if (IS_ERR_OR_NULL(dev->pm_domain)) + genpd = dev_to_genpd(dev); + if (IS_ERR(genpd)) return -EINVAL; - genpd = container_of(dev->pm_domain, struct generic_pm_domain, domain); - if (genpd->parent) mutex_lock(&genpd->parent->lock); mutex_lock_nested(&genpd->lock, SINGLE_DEPTH_NESTING); @@ -201,54 +260,6 @@ static int pm_genpd_runtime_suspend(struct device *dev) } /** - * pm_genpd_poweron - Restore power to a given PM domain and its parents. - * @genpd: PM domain to power up. - * - * Restore power to @genpd and all of its parents so that it is possible to - * resume a device belonging to it. - */ -static int pm_genpd_poweron(struct generic_pm_domain *genpd) -{ - int ret = 0; - - start: - if (genpd->parent) - mutex_lock(&genpd->parent->lock); - mutex_lock_nested(&genpd->lock, SINGLE_DEPTH_NESTING); - - if (!genpd->power_is_off) - goto out; - - if (genpd->parent && genpd->parent->power_is_off) { - mutex_unlock(&genpd->lock); - mutex_unlock(&genpd->parent->lock); - - ret = pm_genpd_poweron(genpd->parent); - if (ret) - return ret; - - goto start; - } - - if (genpd->power_on) { - int ret = genpd->power_on(genpd); - if (ret) - goto out; - } - - genpd->power_is_off = false; - if (genpd->parent) - genpd->parent->sd_count++; - - out: - mutex_unlock(&genpd->lock); - if (genpd->parent) - mutex_unlock(&genpd->parent->lock); - - return ret; -} - -/** * pm_genpd_runtime_resume - Resume a device belonging to I/O PM domain. * @dev: Device to resume. * @@ -264,11 +275,10 @@ static int pm_genpd_runtime_resume(struct device *dev) dev_dbg(dev, "%s()\n", __func__); - if (IS_ERR_OR_NULL(dev->pm_domain)) + genpd = dev_to_genpd(dev); + if (IS_ERR(genpd)) return -EINVAL; - genpd = container_of(dev->pm_domain, struct generic_pm_domain, domain); - ret = pm_genpd_poweron(genpd); if (ret) return ret; -- cgit v0.10.2 From 596ba34bcd2978ee9823cc1d84df230576f8ffb9 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Fri, 1 Jul 2011 22:13:19 +0200 Subject: PM / Domains: System-wide transitions support for generic domains (v5) Make generic PM domains support system-wide power transitions (system suspend and hibernation). Add suspend, resume, freeze, thaw, poweroff and restore callbacks to be associated with struct generic_pm_domain objects and make pm_genpd_init() use them as appropriate. The new callbacks do nothing for devices belonging to power domains that were powered down at run time (before the transition). For the other devices the action carried out depends on the type of the transition. During system suspend the power domain .suspend() callback executes pm_generic_suspend() for the device, while the PM domain .suspend_noirq() callback runs pm_generic_suspend_noirq() for it, stops it and eventually removes power from the PM domain it belongs to (after all devices in the domain have been stopped and its subdomains have been powered off). During system resume the PM domain .resume_noirq() callback restores power to the PM domain (when executed for it first time), starts the device and executes pm_generic_resume_noirq() for it, while the .resume() callback executes pm_generic_resume() for the device. Finally, the .complete() callback executes pm_runtime_idle() for the device which should put it back into the suspended state if its runtime PM usage count is equal to zero at that time. The actions carried out during hibernation and resume from it are analogous to the ones described above. Signed-off-by: Rafael J. Wysocki Reviewed-by: Kevin Hilman diff --git a/drivers/base/power/domain.c b/drivers/base/power/domain.c index f14ba32..33086e9 100644 --- a/drivers/base/power/domain.c +++ b/drivers/base/power/domain.c @@ -21,7 +21,7 @@ static struct generic_pm_domain *dev_to_genpd(struct device *dev) if (IS_ERR_OR_NULL(dev->pm_domain)) return ERR_PTR(-EINVAL); - return container_of(dev->pm_domain, struct generic_pm_domain, domain); + return pd_to_genpd(dev->pm_domain); } static void genpd_sd_counter_dec(struct generic_pm_domain *genpd) @@ -46,7 +46,8 @@ static int pm_genpd_poweron(struct generic_pm_domain *genpd) mutex_lock(&genpd->parent->lock); mutex_lock_nested(&genpd->lock, SINGLE_DEPTH_NESTING); - if (!genpd->power_is_off) + if (!genpd->power_is_off + || (genpd->prepared_count > 0 && genpd->suspend_power_off)) goto out; if (genpd->parent && genpd->parent->power_is_off) { @@ -155,7 +156,7 @@ static int pm_genpd_poweroff(struct generic_pm_domain *genpd) unsigned int not_suspended; int ret; - if (genpd->power_is_off) + if (genpd->power_is_off || genpd->prepared_count > 0) return 0; if (genpd->sd_count > 0) @@ -260,6 +261,27 @@ static int pm_genpd_runtime_suspend(struct device *dev) } /** + * __pm_genpd_runtime_resume - Resume a device belonging to I/O PM domain. + * @dev: Device to resume. + * @genpd: PM domain the device belongs to. + */ +static void __pm_genpd_runtime_resume(struct device *dev, + struct generic_pm_domain *genpd) +{ + struct dev_list_entry *dle; + + list_for_each_entry(dle, &genpd->dev_list, node) { + if (dle->dev == dev) { + __pm_genpd_restore_device(dle, genpd); + break; + } + } + + if (genpd->start_device) + genpd->start_device(dev); +} + +/** * pm_genpd_runtime_resume - Resume a device belonging to I/O PM domain. * @dev: Device to resume. * @@ -270,7 +292,6 @@ static int pm_genpd_runtime_suspend(struct device *dev) static int pm_genpd_runtime_resume(struct device *dev) { struct generic_pm_domain *genpd; - struct dev_list_entry *dle; int ret; dev_dbg(dev, "%s()\n", __func__); @@ -284,17 +305,7 @@ static int pm_genpd_runtime_resume(struct device *dev) return ret; mutex_lock(&genpd->lock); - - list_for_each_entry(dle, &genpd->dev_list, node) { - if (dle->dev == dev) { - __pm_genpd_restore_device(dle, genpd); - break; - } - } - - if (genpd->start_device) - genpd->start_device(dev); - + __pm_genpd_runtime_resume(dev, genpd); mutex_unlock(&genpd->lock); return 0; @@ -303,12 +314,493 @@ static int pm_genpd_runtime_resume(struct device *dev) #else static inline void genpd_power_off_work_fn(struct work_struct *work) {} +static inline void __pm_genpd_runtime_resume(struct device *dev, + struct generic_pm_domain *genpd) {} #define pm_genpd_runtime_suspend NULL #define pm_genpd_runtime_resume NULL #endif /* CONFIG_PM_RUNTIME */ +#ifdef CONFIG_PM_SLEEP + +/** + * pm_genpd_sync_poweroff - Synchronously power off a PM domain and its parents. + * @genpd: PM domain to power off, if possible. + * + * Check if the given PM domain can be powered off (during system suspend or + * hibernation) and do that if so. Also, in that case propagate to its parent. + * + * This function is only called in "noirq" stages of system power transitions, + * so it need not acquire locks (all of the "noirq" callbacks are executed + * sequentially, so it is guaranteed that it will never run twice in parallel). + */ +static void pm_genpd_sync_poweroff(struct generic_pm_domain *genpd) +{ + struct generic_pm_domain *parent = genpd->parent; + + if (genpd->power_is_off) + return; + + if (genpd->suspended_count != genpd->device_count || genpd->sd_count > 0) + return; + + if (genpd->power_off) + genpd->power_off(genpd); + + genpd->power_is_off = true; + if (parent) { + genpd_sd_counter_dec(parent); + pm_genpd_sync_poweroff(parent); + } +} + +/** + * pm_genpd_prepare - Start power transition of a device in a PM domain. + * @dev: Device to start the transition of. + * + * Start a power transition of a device (during a system-wide power transition) + * 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_prepare(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; + + mutex_lock(&genpd->lock); + + if (genpd->prepared_count++ == 0) + genpd->suspend_power_off = genpd->power_is_off; + + if (genpd->suspend_power_off) { + mutex_unlock(&genpd->lock); + return 0; + } + + /* + * If the device is in the (runtime) "suspended" state, call + * .start_device() for it, if defined. + */ + if (pm_runtime_suspended(dev)) + __pm_genpd_runtime_resume(dev, genpd); + + /* + * Do not check if runtime resume is pending at this point, because it + * has been taken care of already and if pm_genpd_poweron() ran at this + * point as a result of the check, it would deadlock. + */ + __pm_runtime_disable(dev, false); + + mutex_unlock(&genpd->lock); + + return pm_generic_prepare(dev); +} + +/** + * pm_genpd_suspend - Suspend a device belonging to an I/O PM domain. + * @dev: Device to suspend. + * + * Suspend 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(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 : pm_generic_suspend(dev); +} + +/** + * pm_genpd_suspend_noirq - 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) +{ + struct generic_pm_domain *genpd; + int ret; + + dev_dbg(dev, "%s()\n", __func__); + + genpd = dev_to_genpd(dev); + if (IS_ERR(genpd)) + return -EINVAL; + + if (genpd->suspend_power_off) + return 0; + + ret = pm_generic_suspend_noirq(dev); + if (ret) + return ret; + + if (genpd->stop_device) + genpd->stop_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. + */ + genpd->suspended_count++; + pm_genpd_sync_poweroff(genpd); + + return 0; +} + +/** + * pm_genpd_resume_noirq - Early resume of a device from an I/O power 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_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 (genpd->suspend_power_off) + return 0; + + /* + * 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. + */ + pm_genpd_poweron(genpd); + genpd->suspended_count--; + if (genpd->start_device) + genpd->start_device(dev); + + return pm_generic_resume_noirq(dev); +} + +/** + * pm_genpd_resume - Resume a device belonging to an I/O power domain. + * @dev: Device to resume. + * + * Resume 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(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 : pm_generic_resume(dev); +} + +/** + * pm_genpd_freeze - Freeze a device belonging to an I/O power domain. + * @dev: Device to freeze. + * + * Freeze 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(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 : pm_generic_freeze(dev); +} + +/** + * pm_genpd_freeze_noirq - Late freeze of a device from an I/O power 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_noirq(struct device *dev) +{ + struct generic_pm_domain *genpd; + int ret; + + dev_dbg(dev, "%s()\n", __func__); + + genpd = dev_to_genpd(dev); + if (IS_ERR(genpd)) + return -EINVAL; + + if (genpd->suspend_power_off) + return 0; + + ret = pm_generic_freeze_noirq(dev); + if (ret) + return ret; + + if (genpd->stop_device) + genpd->stop_device(dev); + + return 0; +} + +/** + * pm_genpd_thaw_noirq - Early thaw of a device from an I/O power domain. + * @dev: Device to thaw. + * + * Carry out an early thaw 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_thaw_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 (genpd->suspend_power_off) + return 0; + + if (genpd->start_device) + genpd->start_device(dev); + + return pm_generic_thaw_noirq(dev); +} + +/** + * pm_genpd_thaw - Thaw a device belonging to an I/O power domain. + * @dev: Device to thaw. + * + * Thaw 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_thaw(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 : pm_generic_thaw(dev); +} + +/** + * pm_genpd_dev_poweroff - Power off a device belonging to an I/O PM domain. + * @dev: Device to suspend. + * + * Power off 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_dev_poweroff(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 : pm_generic_poweroff(dev); +} + +/** + * pm_genpd_dev_poweroff_noirq - Late power off of a device from a PM domain. + * @dev: Device to suspend. + * + * Carry out a late powering off 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_dev_poweroff_noirq(struct device *dev) +{ + struct generic_pm_domain *genpd; + int ret; + + dev_dbg(dev, "%s()\n", __func__); + + genpd = dev_to_genpd(dev); + if (IS_ERR(genpd)) + return -EINVAL; + + if (genpd->suspend_power_off) + return 0; + + ret = pm_generic_poweroff_noirq(dev); + if (ret) + return ret; + + if (genpd->stop_device) + genpd->stop_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. + */ + genpd->suspended_count++; + pm_genpd_sync_poweroff(genpd); + + return 0; +} + +/** + * pm_genpd_restore_noirq - Early restore of a device from an I/O power 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. + */ +static int pm_genpd_restore_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; + + /* + * 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. + */ + genpd->power_is_off = true; + if (genpd->suspend_power_off) { + /* + * The boot kernel might put the domain into the power on state, + * so make sure it really is powered off. + */ + if (genpd->power_off) + genpd->power_off(genpd); + return 0; + } + + pm_genpd_poweron(genpd); + genpd->suspended_count--; + if (genpd->start_device) + genpd->start_device(dev); + + return pm_generic_restore_noirq(dev); +} + +/** + * pm_genpd_restore - Restore a device belonging to an I/O power domain. + * @dev: Device to resume. + * + * Restore 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_restore(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 : pm_generic_restore(dev); +} + +/** + * pm_genpd_complete - Complete power transition of a device in a power domain. + * @dev: Device to complete the transition of. + * + * Complete a power transition of a device (during a system-wide power + * transition) 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 void pm_genpd_complete(struct device *dev) +{ + struct generic_pm_domain *genpd; + bool run_complete; + + dev_dbg(dev, "%s()\n", __func__); + + genpd = dev_to_genpd(dev); + if (IS_ERR(genpd)) + return; + + mutex_lock(&genpd->lock); + + run_complete = !genpd->suspend_power_off; + if (--genpd->prepared_count == 0) + genpd->suspend_power_off = false; + + mutex_unlock(&genpd->lock); + + if (run_complete) { + pm_generic_complete(dev); + pm_runtime_enable(dev); + } +} + +#else + +#define pm_genpd_prepare NULL +#define pm_genpd_suspend NULL +#define pm_genpd_suspend_noirq NULL +#define pm_genpd_resume_noirq NULL +#define pm_genpd_resume NULL +#define pm_genpd_freeze NULL +#define pm_genpd_freeze_noirq NULL +#define pm_genpd_thaw_noirq NULL +#define pm_genpd_thaw NULL +#define pm_genpd_dev_poweroff_noirq NULL +#define pm_genpd_dev_poweroff NULL +#define pm_genpd_restore_noirq NULL +#define pm_genpd_restore NULL +#define pm_genpd_complete NULL + +#endif /* CONFIG_PM_SLEEP */ + /** * pm_genpd_add_device - Add a device to an I/O PM domain. * @genpd: PM domain to add the device to. @@ -331,6 +823,11 @@ int pm_genpd_add_device(struct generic_pm_domain *genpd, struct device *dev) goto out; } + if (genpd->prepared_count > 0) { + ret = -EAGAIN; + goto out; + } + list_for_each_entry(dle, &genpd->dev_list, node) if (dle->dev == dev) { ret = -EINVAL; @@ -346,6 +843,7 @@ int pm_genpd_add_device(struct generic_pm_domain *genpd, struct device *dev) dle->dev = dev; dle->need_restore = false; list_add_tail(&dle->node, &genpd->dev_list); + genpd->device_count++; spin_lock_irq(&dev->power.lock); dev->pm_domain = &genpd->domain; @@ -375,6 +873,11 @@ int pm_genpd_remove_device(struct generic_pm_domain *genpd, mutex_lock(&genpd->lock); + if (genpd->prepared_count > 0) { + ret = -EAGAIN; + goto out; + } + list_for_each_entry(dle, &genpd->dev_list, node) { if (dle->dev != dev) continue; @@ -383,6 +886,7 @@ int pm_genpd_remove_device(struct generic_pm_domain *genpd, dev->pm_domain = NULL; spin_unlock_irq(&dev->power.lock); + genpd->device_count--; list_del(&dle->node); kfree(dle); @@ -390,6 +894,7 @@ int pm_genpd_remove_device(struct generic_pm_domain *genpd, break; } + out: mutex_unlock(&genpd->lock); return ret; @@ -498,7 +1003,23 @@ void pm_genpd_init(struct generic_pm_domain *genpd, genpd->in_progress = 0; genpd->sd_count = 0; genpd->power_is_off = is_off; + genpd->device_count = 0; + genpd->suspended_count = 0; genpd->domain.ops.runtime_suspend = pm_genpd_runtime_suspend; genpd->domain.ops.runtime_resume = pm_genpd_runtime_resume; 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_noirq = pm_genpd_suspend_noirq; + genpd->domain.ops.resume_noirq = pm_genpd_resume_noirq; + genpd->domain.ops.resume = pm_genpd_resume; + genpd->domain.ops.freeze = pm_genpd_freeze; + genpd->domain.ops.freeze_noirq = pm_genpd_freeze_noirq; + genpd->domain.ops.thaw_noirq = pm_genpd_thaw_noirq; + genpd->domain.ops.thaw = pm_genpd_thaw; + genpd->domain.ops.poweroff = pm_genpd_dev_poweroff; + genpd->domain.ops.poweroff_noirq = pm_genpd_dev_poweroff_noirq; + genpd->domain.ops.restore_noirq = pm_genpd_restore_noirq; + genpd->domain.ops.restore = pm_genpd_restore; + genpd->domain.ops.complete = pm_genpd_complete; } diff --git a/include/linux/pm_domain.h b/include/linux/pm_domain.h index b1a22c6..7961b0d 100644 --- a/include/linux/pm_domain.h +++ b/include/linux/pm_domain.h @@ -11,6 +11,9 @@ #include +#define GPD_IN_SUSPEND 1 +#define GPD_POWER_OFF 2 + struct dev_power_governor { bool (*power_down_ok)(struct dev_pm_domain *domain); }; @@ -27,12 +30,21 @@ struct generic_pm_domain { unsigned int in_progress; /* Number of devices being suspended now */ unsigned int sd_count; /* Number of subdomains with power "on" */ bool power_is_off; /* Whether or not power has been removed */ + unsigned int device_count; /* Number of devices */ + unsigned int suspended_count; /* System suspend device counter */ + unsigned int prepared_count; /* Suspend counter of prepared devices */ + bool suspend_power_off; /* Power status before system suspend */ int (*power_off)(struct generic_pm_domain *domain); int (*power_on)(struct generic_pm_domain *domain); int (*start_device)(struct device *dev); int (*stop_device)(struct device *dev); }; +static inline struct generic_pm_domain *pd_to_genpd(struct dev_pm_domain *pd) +{ + return container_of(pd, struct generic_pm_domain, domain); +} + struct dev_list_entry { struct list_head node; struct device *dev; -- cgit v0.10.2 From d4f2d87a8b46c14c4307c690c92bd08229f66ecf Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Fri, 1 Jul 2011 22:13:29 +0200 Subject: PM / Domains: Wakeup devices support for system sleep transitions There is the problem how to handle devices set up to wake up the system from sleep states during system-wide power transitions. In some cases, those devices can be turned off entirely, because the wakeup signals will be generated on their behalf anyway. In some other cases, they will generate wakeup signals if their clocks are stopped, but only if power is not removed from them. Finally, in some cases, they can only generate wakeup signals if power is not removed from them and their clocks are enabled. To allow platform-specific code to decide whether or not to put wakeup devices (and their PM domains) into low-power state during system-wide transitions, such as system suspend, introduce a new generic PM domain callback, .active_wakeup(), that will be used during the "noirq" phase of system suspend and hibernation (after image creation) to decide what to do with wakeup devices. Specifically, if this callback is present and returns "true", the generic PM domain code will not execute .stop_device() for the given wakeup device and its PM domain won't be powered off. Signed-off-by: Rafael J. Wysocki Acked-by: Kevin Hilman diff --git a/drivers/base/power/domain.c b/drivers/base/power/domain.c index 33086e9..1aed94c 100644 --- a/drivers/base/power/domain.c +++ b/drivers/base/power/domain.c @@ -450,6 +450,10 @@ static int pm_genpd_suspend_noirq(struct device *dev) if (ret) return ret; + if (device_may_wakeup(dev) + && genpd->active_wakeup && genpd->active_wakeup(dev)) + return 0; + if (genpd->stop_device) genpd->stop_device(dev); @@ -670,6 +674,10 @@ static int pm_genpd_dev_poweroff_noirq(struct device *dev) if (ret) return ret; + if (device_may_wakeup(dev) + && genpd->active_wakeup && genpd->active_wakeup(dev)) + return 0; + if (genpd->stop_device) genpd->stop_device(dev); diff --git a/include/linux/pm_domain.h b/include/linux/pm_domain.h index 7961b0d..98491ee 100644 --- a/include/linux/pm_domain.h +++ b/include/linux/pm_domain.h @@ -38,6 +38,7 @@ struct generic_pm_domain { int (*power_on)(struct generic_pm_domain *domain); int (*start_device)(struct device *dev); int (*stop_device)(struct device *dev); + bool (*active_wakeup)(struct device *dev); }; static inline struct generic_pm_domain *pd_to_genpd(struct dev_pm_domain *pd) -- cgit v0.10.2 From b7b95920aa2e89e655afe9913ee0e55855ceda90 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Fri, 1 Jul 2011 22:13:37 +0200 Subject: PM: Allow the clocks management code to be used during system suspend The common clocks management code in drivers/base/power/clock_ops.c is going to be used during system-wide power transitions as well as for runtime PM, so it shouldn't depend on CONFIG_PM_RUNTIME. However, the suspend/resume functions provided by it for CONFIG_PM_RUNTIME unset, to be used during system-wide power transitions, should not behave in the same way as their counterparts defined for CONFIG_PM_RUNTIME set, because in that case the clocks are managed differently at run time. The names of the functions still contain the word "runtime" after this change, but that is going to be modified by a separate patch later. Signed-off-by: Rafael J. Wysocki Reviewed-by: Kevin Hilman diff --git a/drivers/base/power/clock_ops.c b/drivers/base/power/clock_ops.c index c562481..2fb9c12 100644 --- a/drivers/base/power/clock_ops.c +++ b/drivers/base/power/clock_ops.c @@ -15,7 +15,7 @@ #include #include -#ifdef CONFIG_PM_RUNTIME +#ifdef CONFIG_PM struct pm_runtime_clk_data { struct list_head clock_list; @@ -191,6 +191,10 @@ void pm_runtime_clk_destroy(struct device *dev) kfree(prd); } +#endif /* CONFIG_PM */ + +#ifdef CONFIG_PM_RUNTIME + /** * pm_runtime_clk_acquire - Acquire a device clock. * @dev: Device whose clock is to be acquired. @@ -330,6 +334,60 @@ static int pm_runtime_clk_notify(struct notifier_block *nb, #else /* !CONFIG_PM_RUNTIME */ +#ifdef CONFIG_PM + +/** + * pm_runtime_clk_suspend - Disable clocks in a device's PM clock list. + * @dev: Device to disable the clocks for. + */ +int pm_runtime_clk_suspend(struct device *dev) +{ + struct pm_runtime_clk_data *prd = __to_prd(dev); + struct pm_clock_entry *ce; + + dev_dbg(dev, "%s()\n", __func__); + + /* If there is no driver, the clocks are already disabled. */ + if (!prd || !dev->driver) + return 0; + + mutex_lock(&prd->lock); + + list_for_each_entry_reverse(ce, &prd->clock_list, node) + clk_disable(ce->clk); + + mutex_unlock(&prd->lock); + + return 0; +} + +/** + * pm_runtime_clk_resume - Enable clocks in a device's PM clock list. + * @dev: Device to enable the clocks for. + */ +int pm_runtime_clk_resume(struct device *dev) +{ + struct pm_runtime_clk_data *prd = __to_prd(dev); + struct pm_clock_entry *ce; + + dev_dbg(dev, "%s()\n", __func__); + + /* If there is no driver, the clocks should remain disabled. */ + if (!prd || !dev->driver) + return 0; + + mutex_lock(&prd->lock); + + list_for_each_entry(ce, &prd->clock_list, node) + clk_enable(ce->clk); + + mutex_unlock(&prd->lock); + + return 0; +} + +#endif /* CONFIG_PM */ + /** * enable_clock - Enable a device clock. * @dev: Device whose clock is to be enabled. diff --git a/include/linux/pm_runtime.h b/include/linux/pm_runtime.h index ef91904..1bd5063 100644 --- a/include/linux/pm_runtime.h +++ b/include/linux/pm_runtime.h @@ -251,7 +251,7 @@ struct pm_clk_notifier_block { char *con_ids[]; }; -#ifdef CONFIG_PM_RUNTIME_CLK +#ifdef CONFIG_PM_CLK extern int pm_runtime_clk_init(struct device *dev); extern void pm_runtime_clk_destroy(struct device *dev); extern int pm_runtime_clk_add(struct device *dev, const char *con_id); diff --git a/kernel/power/Kconfig b/kernel/power/Kconfig index e83ac25..7b856b3 100644 --- a/kernel/power/Kconfig +++ b/kernel/power/Kconfig @@ -224,9 +224,9 @@ config PM_OPP implementations a ready to use framework to manage OPPs. For more information, read -config PM_RUNTIME_CLK +config PM_CLK def_bool y - depends on PM_RUNTIME && HAVE_CLK + depends on PM && HAVE_CLK config PM_GENERIC_DOMAINS bool -- cgit v0.10.2 From 3d5c30367cbc0c55c93bb158e824e00badc7ddc4 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Fri, 1 Jul 2011 22:13:44 +0200 Subject: PM: Rename clock management functions The common PM clock management functions may be used for system suspend/resume as well as for runtime PM, so rename them accordingly. Modify kerneldoc comments describing these functions and kernel messages printed by them, so that they refer to power management in general rather that to runtime PM. Signed-off-by: Rafael J. Wysocki Reviewed-by: Kevin Hilman diff --git a/arch/arm/mach-omap1/pm_bus.c b/arch/arm/mach-omap1/pm_bus.c index 212f331..943072d 100644 --- a/arch/arm/mach-omap1/pm_bus.c +++ b/arch/arm/mach-omap1/pm_bus.c @@ -32,7 +32,7 @@ static int omap1_pm_runtime_suspend(struct device *dev) if (ret) return ret; - ret = pm_runtime_clk_suspend(dev); + ret = pm_clk_suspend(dev); if (ret) { pm_generic_runtime_resume(dev); return ret; @@ -45,7 +45,7 @@ static int omap1_pm_runtime_resume(struct device *dev) { dev_dbg(dev, "%s\n", __func__); - pm_runtime_clk_resume(dev); + pm_clk_resume(dev); return pm_generic_runtime_resume(dev); } @@ -71,7 +71,7 @@ static int __init omap1_pm_runtime_init(void) if (!cpu_class_is_omap1()) return -ENODEV; - pm_runtime_clk_add_notifier(&platform_bus_type, &platform_bus_notifier); + pm_clk_add_notifier(&platform_bus_type, &platform_bus_notifier); return 0; } diff --git a/arch/arm/mach-shmobile/pm_runtime.c b/arch/arm/mach-shmobile/pm_runtime.c index 99802d2..2bcde1c 100644 --- a/arch/arm/mach-shmobile/pm_runtime.c +++ b/arch/arm/mach-shmobile/pm_runtime.c @@ -30,8 +30,8 @@ static int default_platform_runtime_idle(struct device *dev) static struct dev_pm_domain default_pm_domain = { .ops = { - .runtime_suspend = pm_runtime_clk_suspend, - .runtime_resume = pm_runtime_clk_resume, + .runtime_suspend = pm_clk_suspend, + .runtime_resume = pm_clk_resume, .runtime_idle = default_platform_runtime_idle, USE_PLATFORM_PM_SLEEP_OPS }, @@ -52,7 +52,7 @@ static struct pm_clk_notifier_block platform_bus_notifier = { static int __init sh_pm_runtime_init(void) { - pm_runtime_clk_add_notifier(&platform_bus_type, &platform_bus_notifier); + pm_clk_add_notifier(&platform_bus_type, &platform_bus_notifier); return 0; } core_initcall(sh_pm_runtime_init); diff --git a/drivers/base/power/clock_ops.c b/drivers/base/power/clock_ops.c index 2fb9c12..a846b2f 100644 --- a/drivers/base/power/clock_ops.c +++ b/drivers/base/power/clock_ops.c @@ -17,7 +17,7 @@ #ifdef CONFIG_PM -struct pm_runtime_clk_data { +struct pm_clk_data { struct list_head clock_list; struct mutex lock; }; @@ -36,25 +36,25 @@ struct pm_clock_entry { enum pce_status status; }; -static struct pm_runtime_clk_data *__to_prd(struct device *dev) +static struct pm_clk_data *__to_pcd(struct device *dev) { return dev ? dev->power.subsys_data : NULL; } /** - * pm_runtime_clk_add - Start using a device clock for runtime PM. - * @dev: Device whose clock is going to be used for runtime PM. + * pm_clk_add - Start using a device clock for power management. + * @dev: Device whose clock is going to be used for power management. * @con_id: Connection ID of the clock. * * Add the clock represented by @con_id to the list of clocks used for - * the runtime PM of @dev. + * the power management of @dev. */ -int pm_runtime_clk_add(struct device *dev, const char *con_id) +int pm_clk_add(struct device *dev, const char *con_id) { - struct pm_runtime_clk_data *prd = __to_prd(dev); + struct pm_clk_data *pcd = __to_pcd(dev); struct pm_clock_entry *ce; - if (!prd) + if (!pcd) return -EINVAL; ce = kzalloc(sizeof(*ce), GFP_KERNEL); @@ -73,20 +73,20 @@ int pm_runtime_clk_add(struct device *dev, const char *con_id) } } - mutex_lock(&prd->lock); - list_add_tail(&ce->node, &prd->clock_list); - mutex_unlock(&prd->lock); + mutex_lock(&pcd->lock); + list_add_tail(&ce->node, &pcd->clock_list); + mutex_unlock(&pcd->lock); return 0; } /** - * __pm_runtime_clk_remove - Destroy runtime PM clock entry. - * @ce: Runtime PM clock entry to destroy. + * __pm_clk_remove - Destroy PM clock entry. + * @ce: PM clock entry to destroy. * - * This routine must be called under the mutex protecting the runtime PM list - * of clocks corresponding the the @ce's device. + * This routine must be called under the mutex protecting the PM list of clocks + * corresponding the the @ce's device. */ -static void __pm_runtime_clk_remove(struct pm_clock_entry *ce) +static void __pm_clk_remove(struct pm_clock_entry *ce) { if (!ce) return; @@ -108,87 +108,87 @@ static void __pm_runtime_clk_remove(struct pm_clock_entry *ce) } /** - * pm_runtime_clk_remove - Stop using a device clock for runtime PM. - * @dev: Device whose clock should not be used for runtime PM any more. + * pm_clk_remove - Stop using a device clock for power management. + * @dev: Device whose clock should not be used for PM any more. * @con_id: Connection ID of the clock. * * Remove the clock represented by @con_id from the list of clocks used for - * the runtime PM of @dev. + * the power management of @dev. */ -void pm_runtime_clk_remove(struct device *dev, const char *con_id) +void pm_clk_remove(struct device *dev, const char *con_id) { - struct pm_runtime_clk_data *prd = __to_prd(dev); + struct pm_clk_data *pcd = __to_pcd(dev); struct pm_clock_entry *ce; - if (!prd) + if (!pcd) return; - mutex_lock(&prd->lock); + mutex_lock(&pcd->lock); - list_for_each_entry(ce, &prd->clock_list, node) { + list_for_each_entry(ce, &pcd->clock_list, node) { if (!con_id && !ce->con_id) { - __pm_runtime_clk_remove(ce); + __pm_clk_remove(ce); break; } else if (!con_id || !ce->con_id) { continue; } else if (!strcmp(con_id, ce->con_id)) { - __pm_runtime_clk_remove(ce); + __pm_clk_remove(ce); break; } } - mutex_unlock(&prd->lock); + mutex_unlock(&pcd->lock); } /** - * pm_runtime_clk_init - Initialize a device's list of runtime PM clocks. - * @dev: Device to initialize the list of runtime PM clocks for. + * pm_clk_init - Initialize a device's list of power management clocks. + * @dev: Device to initialize the list of PM clocks for. * - * Allocate a struct pm_runtime_clk_data object, initialize its lock member and + * Allocate a struct pm_clk_data object, initialize its lock member and * make the @dev's power.subsys_data field point to it. */ -int pm_runtime_clk_init(struct device *dev) +int pm_clk_init(struct device *dev) { - struct pm_runtime_clk_data *prd; + struct pm_clk_data *pcd; - prd = kzalloc(sizeof(*prd), GFP_KERNEL); - if (!prd) { - dev_err(dev, "Not enough memory fo runtime PM data.\n"); + pcd = kzalloc(sizeof(*pcd), GFP_KERNEL); + if (!pcd) { + dev_err(dev, "Not enough memory for PM clock data.\n"); return -ENOMEM; } - INIT_LIST_HEAD(&prd->clock_list); - mutex_init(&prd->lock); - dev->power.subsys_data = prd; + INIT_LIST_HEAD(&pcd->clock_list); + mutex_init(&pcd->lock); + dev->power.subsys_data = pcd; return 0; } /** - * pm_runtime_clk_destroy - Destroy a device's list of runtime PM clocks. - * @dev: Device to destroy the list of runtime PM clocks for. + * pm_clk_destroy - Destroy a device's list of power management clocks. + * @dev: Device to destroy the list of PM clocks for. * * Clear the @dev's power.subsys_data field, remove the list of clock entries - * from the struct pm_runtime_clk_data object pointed to by it before and free + * from the struct pm_clk_data object pointed to by it before and free * that object. */ -void pm_runtime_clk_destroy(struct device *dev) +void pm_clk_destroy(struct device *dev) { - struct pm_runtime_clk_data *prd = __to_prd(dev); + struct pm_clk_data *pcd = __to_pcd(dev); struct pm_clock_entry *ce, *c; - if (!prd) + if (!pcd) return; dev->power.subsys_data = NULL; - mutex_lock(&prd->lock); + mutex_lock(&pcd->lock); - list_for_each_entry_safe_reverse(ce, c, &prd->clock_list, node) - __pm_runtime_clk_remove(ce); + list_for_each_entry_safe_reverse(ce, c, &pcd->clock_list, node) + __pm_clk_remove(ce); - mutex_unlock(&prd->lock); + mutex_unlock(&pcd->lock); - kfree(prd); + kfree(pcd); } #endif /* CONFIG_PM */ @@ -196,11 +196,11 @@ void pm_runtime_clk_destroy(struct device *dev) #ifdef CONFIG_PM_RUNTIME /** - * pm_runtime_clk_acquire - Acquire a device clock. + * pm_clk_acquire - Acquire a device clock. * @dev: Device whose clock is to be acquired. * @con_id: Connection ID of the clock. */ -static void pm_runtime_clk_acquire(struct device *dev, +static void pm_clk_acquire(struct device *dev, struct pm_clock_entry *ce) { ce->clk = clk_get(dev, ce->con_id); @@ -213,24 +213,24 @@ static void pm_runtime_clk_acquire(struct device *dev, } /** - * pm_runtime_clk_suspend - Disable clocks in a device's runtime PM clock list. + * pm_clk_suspend - Disable clocks in a device's PM clock list. * @dev: Device to disable the clocks for. */ -int pm_runtime_clk_suspend(struct device *dev) +int pm_clk_suspend(struct device *dev) { - struct pm_runtime_clk_data *prd = __to_prd(dev); + struct pm_clk_data *pcd = __to_pcd(dev); struct pm_clock_entry *ce; dev_dbg(dev, "%s()\n", __func__); - if (!prd) + if (!pcd) return 0; - mutex_lock(&prd->lock); + mutex_lock(&pcd->lock); - list_for_each_entry_reverse(ce, &prd->clock_list, node) { + list_for_each_entry_reverse(ce, &pcd->clock_list, node) { if (ce->status == PCE_STATUS_NONE) - pm_runtime_clk_acquire(dev, ce); + pm_clk_acquire(dev, ce); if (ce->status < PCE_STATUS_ERROR) { clk_disable(ce->clk); @@ -238,30 +238,30 @@ int pm_runtime_clk_suspend(struct device *dev) } } - mutex_unlock(&prd->lock); + mutex_unlock(&pcd->lock); return 0; } /** - * pm_runtime_clk_resume - Enable clocks in a device's runtime PM clock list. + * pm_clk_resume - Enable clocks in a device's PM clock list. * @dev: Device to enable the clocks for. */ -int pm_runtime_clk_resume(struct device *dev) +int pm_clk_resume(struct device *dev) { - struct pm_runtime_clk_data *prd = __to_prd(dev); + struct pm_clk_data *pcd = __to_pcd(dev); struct pm_clock_entry *ce; dev_dbg(dev, "%s()\n", __func__); - if (!prd) + if (!pcd) return 0; - mutex_lock(&prd->lock); + mutex_lock(&pcd->lock); - list_for_each_entry(ce, &prd->clock_list, node) { + list_for_each_entry(ce, &pcd->clock_list, node) { if (ce->status == PCE_STATUS_NONE) - pm_runtime_clk_acquire(dev, ce); + pm_clk_acquire(dev, ce); if (ce->status < PCE_STATUS_ERROR) { clk_enable(ce->clk); @@ -269,13 +269,13 @@ int pm_runtime_clk_resume(struct device *dev) } } - mutex_unlock(&prd->lock); + mutex_unlock(&pcd->lock); return 0; } /** - * pm_runtime_clk_notify - Notify routine for device addition and removal. + * pm_clk_notify - Notify routine for device addition and removal. * @nb: Notifier block object this function is a member of. * @action: Operation being carried out by the caller. * @data: Device the routine is being run for. @@ -284,13 +284,13 @@ int pm_runtime_clk_resume(struct device *dev) * struct pm_clk_notifier_block containing all of the requisite data. * Specifically, the pm_domain member of that object is copied to the device's * pm_domain field and its con_ids member is used to populate the device's list - * of runtime PM clocks, depending on @action. + * of PM clocks, depending on @action. * * If the device's pm_domain field is already populated with a value different * from the one stored in the struct pm_clk_notifier_block object, the function * does nothing. */ -static int pm_runtime_clk_notify(struct notifier_block *nb, +static int pm_clk_notify(struct notifier_block *nb, unsigned long action, void *data) { struct pm_clk_notifier_block *clknb; @@ -307,16 +307,16 @@ static int pm_runtime_clk_notify(struct notifier_block *nb, if (dev->pm_domain) break; - error = pm_runtime_clk_init(dev); + error = pm_clk_init(dev); if (error) break; dev->pm_domain = clknb->pm_domain; if (clknb->con_ids[0]) { for (con_id = clknb->con_ids; *con_id; con_id++) - pm_runtime_clk_add(dev, *con_id); + pm_clk_add(dev, *con_id); } else { - pm_runtime_clk_add(dev, NULL); + pm_clk_add(dev, NULL); } break; @@ -325,7 +325,7 @@ static int pm_runtime_clk_notify(struct notifier_block *nb, break; dev->pm_domain = NULL; - pm_runtime_clk_destroy(dev); + pm_clk_destroy(dev); break; } @@ -337,51 +337,51 @@ static int pm_runtime_clk_notify(struct notifier_block *nb, #ifdef CONFIG_PM /** - * pm_runtime_clk_suspend - Disable clocks in a device's PM clock list. + * pm_clk_suspend - Disable clocks in a device's PM clock list. * @dev: Device to disable the clocks for. */ -int pm_runtime_clk_suspend(struct device *dev) +int pm_clk_suspend(struct device *dev) { - struct pm_runtime_clk_data *prd = __to_prd(dev); + struct pm_clk_data *pcd = __to_pcd(dev); struct pm_clock_entry *ce; dev_dbg(dev, "%s()\n", __func__); /* If there is no driver, the clocks are already disabled. */ - if (!prd || !dev->driver) + if (!pcd || !dev->driver) return 0; - mutex_lock(&prd->lock); + mutex_lock(&pcd->lock); - list_for_each_entry_reverse(ce, &prd->clock_list, node) + list_for_each_entry_reverse(ce, &pcd->clock_list, node) clk_disable(ce->clk); - mutex_unlock(&prd->lock); + mutex_unlock(&pcd->lock); return 0; } /** - * pm_runtime_clk_resume - Enable clocks in a device's PM clock list. + * pm_clk_resume - Enable clocks in a device's PM clock list. * @dev: Device to enable the clocks for. */ -int pm_runtime_clk_resume(struct device *dev) +int pm_clk_resume(struct device *dev) { - struct pm_runtime_clk_data *prd = __to_prd(dev); + struct pm_clk_data *pcd = __to_pcd(dev); struct pm_clock_entry *ce; dev_dbg(dev, "%s()\n", __func__); /* If there is no driver, the clocks should remain disabled. */ - if (!prd || !dev->driver) + if (!pcd || !dev->driver) return 0; - mutex_lock(&prd->lock); + mutex_lock(&pcd->lock); - list_for_each_entry(ce, &prd->clock_list, node) + list_for_each_entry(ce, &pcd->clock_list, node) clk_enable(ce->clk); - mutex_unlock(&prd->lock); + mutex_unlock(&pcd->lock); return 0; } @@ -423,7 +423,7 @@ static void disable_clock(struct device *dev, const char *con_id) } /** - * pm_runtime_clk_notify - Notify routine for device addition and removal. + * pm_clk_notify - Notify routine for device addition and removal. * @nb: Notifier block object this function is a member of. * @action: Operation being carried out by the caller. * @data: Device the routine is being run for. @@ -433,7 +433,7 @@ static void disable_clock(struct device *dev, const char *con_id) * Specifically, the con_ids member of that object is used to enable or disable * the device's clocks, depending on @action. */ -static int pm_runtime_clk_notify(struct notifier_block *nb, +static int pm_clk_notify(struct notifier_block *nb, unsigned long action, void *data) { struct pm_clk_notifier_block *clknb; @@ -469,21 +469,21 @@ static int pm_runtime_clk_notify(struct notifier_block *nb, #endif /* !CONFIG_PM_RUNTIME */ /** - * pm_runtime_clk_add_notifier - Add bus type notifier for runtime PM clocks. + * pm_clk_add_notifier - Add bus type notifier for power management clocks. * @bus: Bus type to add the notifier to. * @clknb: Notifier to be added to the given bus type. * * The nb member of @clknb is not expected to be initialized and its - * notifier_call member will be replaced with pm_runtime_clk_notify(). However, + * notifier_call member will be replaced with pm_clk_notify(). However, * the remaining members of @clknb should be populated prior to calling this * routine. */ -void pm_runtime_clk_add_notifier(struct bus_type *bus, +void pm_clk_add_notifier(struct bus_type *bus, struct pm_clk_notifier_block *clknb) { if (!bus || !clknb) return; - clknb->nb.notifier_call = pm_runtime_clk_notify; + clknb->nb.notifier_call = pm_clk_notify; bus_register_notifier(bus, &clknb->nb); } diff --git a/include/linux/pm_runtime.h b/include/linux/pm_runtime.h index 1bd5063..dfb8539 100644 --- a/include/linux/pm_runtime.h +++ b/include/linux/pm_runtime.h @@ -252,36 +252,36 @@ struct pm_clk_notifier_block { }; #ifdef CONFIG_PM_CLK -extern int pm_runtime_clk_init(struct device *dev); -extern void pm_runtime_clk_destroy(struct device *dev); -extern int pm_runtime_clk_add(struct device *dev, const char *con_id); -extern void pm_runtime_clk_remove(struct device *dev, const char *con_id); -extern int pm_runtime_clk_suspend(struct device *dev); -extern int pm_runtime_clk_resume(struct device *dev); +extern int pm_clk_init(struct device *dev); +extern void pm_clk_destroy(struct device *dev); +extern int pm_clk_add(struct device *dev, const char *con_id); +extern void pm_clk_remove(struct device *dev, const char *con_id); +extern int pm_clk_suspend(struct device *dev); +extern int pm_clk_resume(struct device *dev); #else -static inline int pm_runtime_clk_init(struct device *dev) +static inline int pm_clk_init(struct device *dev) { return -EINVAL; } -static inline void pm_runtime_clk_destroy(struct device *dev) +static inline void pm_clk_destroy(struct device *dev) { } -static inline int pm_runtime_clk_add(struct device *dev, const char *con_id) +static inline int pm_clk_add(struct device *dev, const char *con_id) { return -EINVAL; } -static inline void pm_runtime_clk_remove(struct device *dev, const char *con_id) +static inline void pm_clk_remove(struct device *dev, const char *con_id) { } -#define pm_runtime_clock_suspend NULL -#define pm_runtime_clock_resume NULL +#define pm_clk_suspend NULL +#define pm_clk_resume NULL #endif #ifdef CONFIG_HAVE_CLK -extern void pm_runtime_clk_add_notifier(struct bus_type *bus, +extern void pm_clk_add_notifier(struct bus_type *bus, struct pm_clk_notifier_block *clknb); #else -static inline void pm_runtime_clk_add_notifier(struct bus_type *bus, +static inline void pm_clk_add_notifier(struct bus_type *bus, struct pm_clk_notifier_block *clknb) { } -- cgit v0.10.2 From e3e0109138376bb262b8ecf33bad0586fa131925 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Fri, 1 Jul 2011 22:13:56 +0200 Subject: ARM / shmobile: Support for I/O power domains for SH7372 (v9) Use the generic power domains support introduced by the previous patch to implement support for power domains on SH7372. Signed-off-by: Rafael J. Wysocki Acked-by: Paul Mundt diff --git a/arch/arm/Kconfig b/arch/arm/Kconfig index 9adc278..e04fa9d 100644 --- a/arch/arm/Kconfig +++ b/arch/arm/Kconfig @@ -642,6 +642,7 @@ config ARCH_SHMOBILE select NO_IOPORT select SPARSE_IRQ select MULTI_IRQ_HANDLER + select PM_GENERIC_DOMAINS if PM help Support for Renesas's SH-Mobile and R-Mobile ARM platforms. diff --git a/arch/arm/mach-shmobile/board-mackerel.c b/arch/arm/mach-shmobile/board-mackerel.c index 3802f2a..f93b1cb 100644 --- a/arch/arm/mach-shmobile/board-mackerel.c +++ b/arch/arm/mach-shmobile/board-mackerel.c @@ -1582,6 +1582,10 @@ static void __init mackerel_init(void) platform_add_devices(mackerel_devices, ARRAY_SIZE(mackerel_devices)); + sh7372_init_pm_domain(&sh7372_a4lc); + sh7372_add_device_to_domain(&sh7372_a4lc, &lcdc_device); + sh7372_add_device_to_domain(&sh7372_a4lc, &hdmi_lcdc_device); + hdmi_init_pm_clock(); sh7372_pm_init(); } diff --git a/arch/arm/mach-shmobile/include/mach/sh7372.h b/arch/arm/mach-shmobile/include/mach/sh7372.h index df20d76..495013c 100644 --- a/arch/arm/mach-shmobile/include/mach/sh7372.h +++ b/arch/arm/mach-shmobile/include/mach/sh7372.h @@ -12,6 +12,7 @@ #define __ASM_SH7372_H__ #include +#include /* * Pin Function Controller: @@ -470,4 +471,27 @@ extern struct clk sh7372_fsibck_clk; extern struct clk sh7372_fsidiva_clk; extern struct clk sh7372_fsidivb_clk; +struct platform_device; + +struct sh7372_pm_domain { + struct generic_pm_domain genpd; + unsigned int bit_shift; +}; + +static inline struct sh7372_pm_domain *to_sh7372_pd(struct generic_pm_domain *d) +{ + return container_of(d, struct sh7372_pm_domain, genpd); +} + +#ifdef CONFIG_PM +extern struct sh7372_pm_domain sh7372_a4lc; + +extern void sh7372_init_pm_domain(struct sh7372_pm_domain *sh7372_pd); +extern void sh7372_add_device_to_domain(struct sh7372_pm_domain *sh7372_pd, + struct platform_device *pdev); +#else +#define sh7372_init_pm_domain(pd) do { } while(0) +#define sh7372_add_device_to_domain(pd, pdev) do { } while(0) +#endif /* CONFIG_PM */ + #endif /* __ASM_SH7372_H__ */ diff --git a/arch/arm/mach-shmobile/pm-sh7372.c b/arch/arm/mach-shmobile/pm-sh7372.c index 8e4aadf..e17f124 100644 --- a/arch/arm/mach-shmobile/pm-sh7372.c +++ b/arch/arm/mach-shmobile/pm-sh7372.c @@ -15,16 +15,118 @@ #include #include #include +#include +#include +#include #include #include #include #include +#include #define SMFRAM 0xe6a70000 #define SYSTBCR 0xe6150024 #define SBAR 0xe6180020 #define APARMBAREA 0xe6f10020 +#define SPDCR 0xe6180008 +#define SWUCR 0xe6180014 +#define PSTR 0xe6180080 + +#define PSTR_RETRIES 100 +#define PSTR_DELAY_US 10 + +#ifdef CONFIG_PM + +static int pd_power_down(struct generic_pm_domain *genpd) +{ + struct sh7372_pm_domain *sh7372_pd = to_sh7372_pd(genpd); + unsigned int mask = 1 << sh7372_pd->bit_shift; + + if (__raw_readl(PSTR) & mask) { + unsigned int retry_count; + + __raw_writel(mask, SPDCR); + + for (retry_count = PSTR_RETRIES; retry_count; retry_count--) { + if (!(__raw_readl(SPDCR) & mask)) + break; + cpu_relax(); + } + } + + pr_debug("sh7372 power domain down 0x%08x -> PSTR = 0x%08x\n", + mask, __raw_readl(PSTR)); + + return 0; +} + +static int pd_power_up(struct generic_pm_domain *genpd) +{ + struct sh7372_pm_domain *sh7372_pd = to_sh7372_pd(genpd); + unsigned int mask = 1 << sh7372_pd->bit_shift; + unsigned int retry_count; + int ret = 0; + + if (__raw_readl(PSTR) & mask) + goto out; + + __raw_writel(mask, SWUCR); + + for (retry_count = 2 * PSTR_RETRIES; retry_count; retry_count--) { + if (!(__raw_readl(SWUCR) & mask)) + goto out; + if (retry_count > PSTR_RETRIES) + udelay(PSTR_DELAY_US); + else + cpu_relax(); + } + if (__raw_readl(SWUCR) & mask) + ret = -EIO; + + out: + pr_debug("sh7372 power domain up 0x%08x -> PSTR = 0x%08x\n", + mask, __raw_readl(PSTR)); + + return ret; +} + +static bool pd_active_wakeup(struct device *dev) +{ + return true; +} + +void sh7372_init_pm_domain(struct sh7372_pm_domain *sh7372_pd) +{ + struct generic_pm_domain *genpd = &sh7372_pd->genpd; + + pm_genpd_init(genpd, NULL, false); + genpd->stop_device = pm_clk_suspend; + genpd->start_device = pm_clk_resume; + genpd->active_wakeup = pd_active_wakeup; + genpd->power_off = pd_power_down; + genpd->power_on = pd_power_up; + pd_power_up(&sh7372_pd->genpd); +} + +void sh7372_add_device_to_domain(struct sh7372_pm_domain *sh7372_pd, + struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + + if (!dev->power.subsys_data) { + pm_clk_init(dev); + pm_clk_add(dev, NULL); + } + pm_genpd_add_device(&sh7372_pd->genpd, dev); +} + +struct sh7372_pm_domain sh7372_a4lc = { + .bit_shift = 1, +}; + +#endif /* CONFIG_PM */ + static void sh7372_enter_core_standby(void) { void __iomem *smfram = (void __iomem *)SMFRAM; -- cgit v0.10.2 From 96f7934e448fd52644b8862ea12761e1524f3bf3 Mon Sep 17 00:00:00 2001 From: Magnus Damm Date: Fri, 1 Jul 2011 22:14:34 +0200 Subject: ARM: mach-shmobile: sh7372 A4LC support for AP4EVB The AP4EVB board is also using a sh7372 SoC, so tie in A4LC support on that board as well. Signed-off-by: Magnus Damm Signed-off-by: Rafael J. Wysocki diff --git a/arch/arm/mach-shmobile/board-ap4evb.c b/arch/arm/mach-shmobile/board-ap4evb.c index 803bc6e..a26f895 100644 --- a/arch/arm/mach-shmobile/board-ap4evb.c +++ b/arch/arm/mach-shmobile/board-ap4evb.c @@ -1408,6 +1408,9 @@ static void __init ap4evb_init(void) platform_add_devices(ap4evb_devices, ARRAY_SIZE(ap4evb_devices)); + sh7372_add_device_to_domain(&sh7372_a4lc, &lcdc1_device); + sh7372_add_device_to_domain(&sh7372_a4lc, &lcdc_device); + hdmi_init_pm_clock(); fsi_init_pm_clock(); sh7372_pm_init(); diff --git a/arch/arm/mach-shmobile/board-mackerel.c b/arch/arm/mach-shmobile/board-mackerel.c index f93b1cb..cc1ccd8 100644 --- a/arch/arm/mach-shmobile/board-mackerel.c +++ b/arch/arm/mach-shmobile/board-mackerel.c @@ -1582,7 +1582,6 @@ static void __init mackerel_init(void) platform_add_devices(mackerel_devices, ARRAY_SIZE(mackerel_devices)); - sh7372_init_pm_domain(&sh7372_a4lc); sh7372_add_device_to_domain(&sh7372_a4lc, &lcdc_device); sh7372_add_device_to_domain(&sh7372_a4lc, &hdmi_lcdc_device); diff --git a/arch/arm/mach-shmobile/setup-sh7372.c b/arch/arm/mach-shmobile/setup-sh7372.c index cd807ee..ec1ae8c 100644 --- a/arch/arm/mach-shmobile/setup-sh7372.c +++ b/arch/arm/mach-shmobile/setup-sh7372.c @@ -841,6 +841,8 @@ static struct platform_device *sh7372_late_devices[] __initdata = { void __init sh7372_add_standard_devices(void) { + sh7372_init_pm_domain(&sh7372_a4lc); + platform_add_devices(sh7372_early_devices, ARRAY_SIZE(sh7372_early_devices)); -- cgit v0.10.2 From 33afebf3da2e8c26faebb1b271c810f937e35788 Mon Sep 17 00:00:00 2001 From: Magnus Damm Date: Fri, 1 Jul 2011 22:14:45 +0200 Subject: ARM: mach-shmobile: sh7372 A3RV support Add support for the sh7372 A3RV power domain and hook up the VPU device. Signed-off-by: Magnus Damm Signed-off-by: Rafael J. Wysocki diff --git a/arch/arm/mach-shmobile/include/mach/sh7372.h b/arch/arm/mach-shmobile/include/mach/sh7372.h index 495013c..e935b5e 100644 --- a/arch/arm/mach-shmobile/include/mach/sh7372.h +++ b/arch/arm/mach-shmobile/include/mach/sh7372.h @@ -485,6 +485,7 @@ static inline struct sh7372_pm_domain *to_sh7372_pd(struct generic_pm_domain *d) #ifdef CONFIG_PM extern struct sh7372_pm_domain sh7372_a4lc; +extern struct sh7372_pm_domain sh7372_a3rv; extern void sh7372_init_pm_domain(struct sh7372_pm_domain *sh7372_pd); extern void sh7372_add_device_to_domain(struct sh7372_pm_domain *sh7372_pd, diff --git a/arch/arm/mach-shmobile/pm-sh7372.c b/arch/arm/mach-shmobile/pm-sh7372.c index e17f124..d467c86 100644 --- a/arch/arm/mach-shmobile/pm-sh7372.c +++ b/arch/arm/mach-shmobile/pm-sh7372.c @@ -125,6 +125,10 @@ struct sh7372_pm_domain sh7372_a4lc = { .bit_shift = 1, }; +struct sh7372_pm_domain sh7372_a3rv = { + .bit_shift = 6, +}; + #endif /* CONFIG_PM */ static void sh7372_enter_core_standby(void) diff --git a/arch/arm/mach-shmobile/setup-sh7372.c b/arch/arm/mach-shmobile/setup-sh7372.c index ec1ae8c..14a497f 100644 --- a/arch/arm/mach-shmobile/setup-sh7372.c +++ b/arch/arm/mach-shmobile/setup-sh7372.c @@ -842,12 +842,15 @@ static struct platform_device *sh7372_late_devices[] __initdata = { void __init sh7372_add_standard_devices(void) { sh7372_init_pm_domain(&sh7372_a4lc); + sh7372_init_pm_domain(&sh7372_a3rv); platform_add_devices(sh7372_early_devices, ARRAY_SIZE(sh7372_early_devices)); platform_add_devices(sh7372_late_devices, ARRAY_SIZE(sh7372_late_devices)); + + sh7372_add_device_to_domain(&sh7372_a3rv, &vpu_device); } void __init sh7372_add_early_devices(void) -- cgit v0.10.2 From 082517aa21c60c06bd2a4c287aab5e1b3d8c7c22 Mon Sep 17 00:00:00 2001 From: Magnus Damm Date: Fri, 1 Jul 2011 22:14:53 +0200 Subject: ARM: mach-shmobile: sh7372 A3RI support Add support for the sh7372 A3RI power domain. This domain contains the ISP hardware block, but there is no driver available. Signed-off-by: Magnus Damm Signed-off-by: Rafael J. Wysocki diff --git a/arch/arm/mach-shmobile/include/mach/sh7372.h b/arch/arm/mach-shmobile/include/mach/sh7372.h index e935b5e..b99f3d6 100644 --- a/arch/arm/mach-shmobile/include/mach/sh7372.h +++ b/arch/arm/mach-shmobile/include/mach/sh7372.h @@ -486,6 +486,7 @@ static inline struct sh7372_pm_domain *to_sh7372_pd(struct generic_pm_domain *d) #ifdef CONFIG_PM extern struct sh7372_pm_domain sh7372_a4lc; extern struct sh7372_pm_domain sh7372_a3rv; +extern struct sh7372_pm_domain sh7372_a3ri; extern void sh7372_init_pm_domain(struct sh7372_pm_domain *sh7372_pd); extern void sh7372_add_device_to_domain(struct sh7372_pm_domain *sh7372_pd, diff --git a/arch/arm/mach-shmobile/pm-sh7372.c b/arch/arm/mach-shmobile/pm-sh7372.c index d467c86..48c3b91 100644 --- a/arch/arm/mach-shmobile/pm-sh7372.c +++ b/arch/arm/mach-shmobile/pm-sh7372.c @@ -129,6 +129,10 @@ struct sh7372_pm_domain sh7372_a3rv = { .bit_shift = 6, }; +struct sh7372_pm_domain sh7372_a3ri = { + .bit_shift = 8, +}; + #endif /* CONFIG_PM */ static void sh7372_enter_core_standby(void) diff --git a/arch/arm/mach-shmobile/setup-sh7372.c b/arch/arm/mach-shmobile/setup-sh7372.c index 14a497f..d558333 100644 --- a/arch/arm/mach-shmobile/setup-sh7372.c +++ b/arch/arm/mach-shmobile/setup-sh7372.c @@ -843,6 +843,7 @@ void __init sh7372_add_standard_devices(void) { sh7372_init_pm_domain(&sh7372_a4lc); sh7372_init_pm_domain(&sh7372_a3rv); + sh7372_init_pm_domain(&sh7372_a3ri); platform_add_devices(sh7372_early_devices, ARRAY_SIZE(sh7372_early_devices)); -- cgit v0.10.2 From c47586b6d36ef2d5d7dc39afc44b75e31bc1a671 Mon Sep 17 00:00:00 2001 From: Magnus Damm Date: Fri, 1 Jul 2011 22:15:01 +0200 Subject: ARM: mach-shmobile: sh7372 A3SG support Add support for the sh7372 A3SG power domain. This domain contains the SGX hardware block, but there is no open source driver available. Signed-off-by: Magnus Damm Signed-off-by: Rafael J. Wysocki diff --git a/arch/arm/mach-shmobile/include/mach/sh7372.h b/arch/arm/mach-shmobile/include/mach/sh7372.h index b99f3d6..1aed9da 100644 --- a/arch/arm/mach-shmobile/include/mach/sh7372.h +++ b/arch/arm/mach-shmobile/include/mach/sh7372.h @@ -487,6 +487,7 @@ static inline struct sh7372_pm_domain *to_sh7372_pd(struct generic_pm_domain *d) extern struct sh7372_pm_domain sh7372_a4lc; extern struct sh7372_pm_domain sh7372_a3rv; extern struct sh7372_pm_domain sh7372_a3ri; +extern struct sh7372_pm_domain sh7372_a3sg; extern void sh7372_init_pm_domain(struct sh7372_pm_domain *sh7372_pd); extern void sh7372_add_device_to_domain(struct sh7372_pm_domain *sh7372_pd, diff --git a/arch/arm/mach-shmobile/pm-sh7372.c b/arch/arm/mach-shmobile/pm-sh7372.c index 48c3b91..474a15a 100644 --- a/arch/arm/mach-shmobile/pm-sh7372.c +++ b/arch/arm/mach-shmobile/pm-sh7372.c @@ -133,6 +133,10 @@ struct sh7372_pm_domain sh7372_a3ri = { .bit_shift = 8, }; +struct sh7372_pm_domain sh7372_a3sg = { + .bit_shift = 13, +}; + #endif /* CONFIG_PM */ static void sh7372_enter_core_standby(void) diff --git a/arch/arm/mach-shmobile/setup-sh7372.c b/arch/arm/mach-shmobile/setup-sh7372.c index d558333..b219cea 100644 --- a/arch/arm/mach-shmobile/setup-sh7372.c +++ b/arch/arm/mach-shmobile/setup-sh7372.c @@ -844,6 +844,7 @@ void __init sh7372_add_standard_devices(void) sh7372_init_pm_domain(&sh7372_a4lc); sh7372_init_pm_domain(&sh7372_a3rv); sh7372_init_pm_domain(&sh7372_a3ri); + sh7372_init_pm_domain(&sh7372_a3sg); platform_add_devices(sh7372_early_devices, ARRAY_SIZE(sh7372_early_devices)); -- cgit v0.10.2 From a41b64665a79b8be980d450f6e5b9354d7511700 Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Sun, 10 Jul 2011 10:11:57 +0200 Subject: ARM: mach-shmobile: sh7372: make sure that fsi is peripheral of spu2 FSI act as peripheral circuits of the SPU2. Signed-off-by: Kuninori Morimoto Acked-by: Magnus Damm Signed-off-by: Rafael J. Wysocki diff --git a/arch/arm/mach-shmobile/board-ap4evb.c b/arch/arm/mach-shmobile/board-ap4evb.c index a26f895..4226db6 100644 --- a/arch/arm/mach-shmobile/board-ap4evb.c +++ b/arch/arm/mach-shmobile/board-ap4evb.c @@ -1414,6 +1414,7 @@ static void __init ap4evb_init(void) hdmi_init_pm_clock(); fsi_init_pm_clock(); sh7372_pm_init(); + pm_clk_add(&fsi_device.dev, "spu2"); } static void __init ap4evb_timer_init(void) diff --git a/arch/arm/mach-shmobile/board-mackerel.c b/arch/arm/mach-shmobile/board-mackerel.c index cc1ccd8..362b883 100644 --- a/arch/arm/mach-shmobile/board-mackerel.c +++ b/arch/arm/mach-shmobile/board-mackerel.c @@ -1587,6 +1587,7 @@ static void __init mackerel_init(void) hdmi_init_pm_clock(); sh7372_pm_init(); + pm_clk_add(&fsi_device.dev, "spu2"); } static void __init mackerel_timer_init(void) diff --git a/arch/arm/mach-shmobile/clock-sh7372.c b/arch/arm/mach-shmobile/clock-sh7372.c index c0800d8..91f5779 100644 --- a/arch/arm/mach-shmobile/clock-sh7372.c +++ b/arch/arm/mach-shmobile/clock-sh7372.c @@ -662,6 +662,7 @@ static struct clk_lookup lookups[] = { CLKDEV_ICK_ID("ick", "sh-mobile-hdmi", &div6_reparent_clks[DIV6_HDMI]), CLKDEV_ICK_ID("icka", "sh_fsi2", &div6_reparent_clks[DIV6_FSIA]), CLKDEV_ICK_ID("ickb", "sh_fsi2", &div6_reparent_clks[DIV6_FSIB]), + CLKDEV_ICK_ID("spu2", "sh_fsi2", &mstp_clks[MSTP223]), }; void __init sh7372_clock_init(void) -- cgit v0.10.2 From c1ba5bb5608664b37a3b1ac647e0dd67d46ddc99 Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Sun, 10 Jul 2011 10:12:08 +0200 Subject: ARM: mach-shmobile: sh7372 A4MP support Add support for the sh7372 A4MP power domain and hook up the FSI/SPU2 device. Signed-off-by: Kuninori Morimoto Acked-by: Magnus Damm Signed-off-by: Rafael J. Wysocki diff --git a/arch/arm/mach-shmobile/board-ap4evb.c b/arch/arm/mach-shmobile/board-ap4evb.c index 4226db6..b473b8e 100644 --- a/arch/arm/mach-shmobile/board-ap4evb.c +++ b/arch/arm/mach-shmobile/board-ap4evb.c @@ -1410,6 +1410,7 @@ static void __init ap4evb_init(void) sh7372_add_device_to_domain(&sh7372_a4lc, &lcdc1_device); sh7372_add_device_to_domain(&sh7372_a4lc, &lcdc_device); + sh7372_add_device_to_domain(&sh7372_a4mp, &fsi_device); hdmi_init_pm_clock(); fsi_init_pm_clock(); diff --git a/arch/arm/mach-shmobile/board-mackerel.c b/arch/arm/mach-shmobile/board-mackerel.c index 362b883..5b36b6c 100644 --- a/arch/arm/mach-shmobile/board-mackerel.c +++ b/arch/arm/mach-shmobile/board-mackerel.c @@ -1584,6 +1584,7 @@ static void __init mackerel_init(void) sh7372_add_device_to_domain(&sh7372_a4lc, &lcdc_device); sh7372_add_device_to_domain(&sh7372_a4lc, &hdmi_lcdc_device); + sh7372_add_device_to_domain(&sh7372_a4mp, &fsi_device); hdmi_init_pm_clock(); sh7372_pm_init(); diff --git a/arch/arm/mach-shmobile/include/mach/sh7372.h b/arch/arm/mach-shmobile/include/mach/sh7372.h index 1aed9da..f3ceced 100644 --- a/arch/arm/mach-shmobile/include/mach/sh7372.h +++ b/arch/arm/mach-shmobile/include/mach/sh7372.h @@ -485,6 +485,7 @@ static inline struct sh7372_pm_domain *to_sh7372_pd(struct generic_pm_domain *d) #ifdef CONFIG_PM extern struct sh7372_pm_domain sh7372_a4lc; +extern struct sh7372_pm_domain sh7372_a4mp; extern struct sh7372_pm_domain sh7372_a3rv; extern struct sh7372_pm_domain sh7372_a3ri; extern struct sh7372_pm_domain sh7372_a3sg; diff --git a/arch/arm/mach-shmobile/pm-sh7372.c b/arch/arm/mach-shmobile/pm-sh7372.c index 474a15a..f3bd95f 100644 --- a/arch/arm/mach-shmobile/pm-sh7372.c +++ b/arch/arm/mach-shmobile/pm-sh7372.c @@ -125,6 +125,10 @@ struct sh7372_pm_domain sh7372_a4lc = { .bit_shift = 1, }; +struct sh7372_pm_domain sh7372_a4mp = { + .bit_shift = 2, +}; + struct sh7372_pm_domain sh7372_a3rv = { .bit_shift = 6, }; diff --git a/arch/arm/mach-shmobile/setup-sh7372.c b/arch/arm/mach-shmobile/setup-sh7372.c index b219cea..9b0cd43 100644 --- a/arch/arm/mach-shmobile/setup-sh7372.c +++ b/arch/arm/mach-shmobile/setup-sh7372.c @@ -842,6 +842,7 @@ static struct platform_device *sh7372_late_devices[] __initdata = { void __init sh7372_add_standard_devices(void) { sh7372_init_pm_domain(&sh7372_a4lc); + sh7372_init_pm_domain(&sh7372_a4mp); sh7372_init_pm_domain(&sh7372_a3rv); sh7372_init_pm_domain(&sh7372_a3ri); sh7372_init_pm_domain(&sh7372_a3sg); @@ -853,6 +854,8 @@ void __init sh7372_add_standard_devices(void) ARRAY_SIZE(sh7372_late_devices)); sh7372_add_device_to_domain(&sh7372_a3rv, &vpu_device); + sh7372_add_device_to_domain(&sh7372_a4mp, &spu0_device); + sh7372_add_device_to_domain(&sh7372_a4mp, &spu1_device); } void __init sh7372_add_early_devices(void) -- cgit v0.10.2 From d24771dec9c62945a5d1c6a37e7a04f5c2a2ae6f Mon Sep 17 00:00:00 2001 From: Magnus Damm Date: Sun, 10 Jul 2011 10:38:22 +0200 Subject: ARM: mach-shmobile: sh7372 D4 support Add support for the sh7372 D4 power domain. This power domain contains the Coresight-ETM hardware block. Signed-off-by: Magnus Damm Signed-off-by: Rafael J. Wysocki diff --git a/arch/arm/mach-shmobile/include/mach/sh7372.h b/arch/arm/mach-shmobile/include/mach/sh7372.h index f3ceced..ce595ce 100644 --- a/arch/arm/mach-shmobile/include/mach/sh7372.h +++ b/arch/arm/mach-shmobile/include/mach/sh7372.h @@ -486,6 +486,7 @@ static inline struct sh7372_pm_domain *to_sh7372_pd(struct generic_pm_domain *d) #ifdef CONFIG_PM extern struct sh7372_pm_domain sh7372_a4lc; extern struct sh7372_pm_domain sh7372_a4mp; +extern struct sh7372_pm_domain sh7372_d4; extern struct sh7372_pm_domain sh7372_a3rv; extern struct sh7372_pm_domain sh7372_a3ri; extern struct sh7372_pm_domain sh7372_a3sg; diff --git a/arch/arm/mach-shmobile/pm-sh7372.c b/arch/arm/mach-shmobile/pm-sh7372.c index f3bd95f..a8e0de1 100644 --- a/arch/arm/mach-shmobile/pm-sh7372.c +++ b/arch/arm/mach-shmobile/pm-sh7372.c @@ -129,6 +129,10 @@ struct sh7372_pm_domain sh7372_a4mp = { .bit_shift = 2, }; +struct sh7372_pm_domain sh7372_d4 = { + .bit_shift = 3, +}; + struct sh7372_pm_domain sh7372_a3rv = { .bit_shift = 6, }; diff --git a/arch/arm/mach-shmobile/setup-sh7372.c b/arch/arm/mach-shmobile/setup-sh7372.c index 9b0cd43..79f0413 100644 --- a/arch/arm/mach-shmobile/setup-sh7372.c +++ b/arch/arm/mach-shmobile/setup-sh7372.c @@ -843,6 +843,7 @@ void __init sh7372_add_standard_devices(void) { sh7372_init_pm_domain(&sh7372_a4lc); sh7372_init_pm_domain(&sh7372_a4mp); + sh7372_init_pm_domain(&sh7372_d4); sh7372_init_pm_domain(&sh7372_a3rv); sh7372_init_pm_domain(&sh7372_a3ri); sh7372_init_pm_domain(&sh7372_a3sg); -- cgit v0.10.2 From 999a4d2a4da0527567e4b17d4da0782509358a83 Mon Sep 17 00:00:00 2001 From: Magnus Damm Date: Sun, 10 Jul 2011 10:38:34 +0200 Subject: ARM: mach-shmobile: Runtime PM late init callback Add a mach-shmobile specific callback for SoC-specific code to hook into. By having the late_initcall() in a common place we can have multi-SoC/board support in the same kernel binary. Signed-off-by: Magnus Damm Signed-off-by: Rafael J. Wysocki diff --git a/arch/arm/mach-shmobile/include/mach/common.h b/arch/arm/mach-shmobile/include/mach/common.h index 06aecb3..73a76d7 100644 --- a/arch/arm/mach-shmobile/include/mach/common.h +++ b/arch/arm/mach-shmobile/include/mach/common.h @@ -12,6 +12,7 @@ extern struct platform_suspend_ops shmobile_suspend_ops; struct cpuidle_device; extern void (*shmobile_cpuidle_modes[])(void); extern void (*shmobile_cpuidle_setup)(struct cpuidle_device *dev); +extern void (*shmobile_runtime_pm_late_init)(void); extern void sh7367_init_irq(void); extern void sh7367_add_early_devices(void); diff --git a/arch/arm/mach-shmobile/pm_runtime.c b/arch/arm/mach-shmobile/pm_runtime.c index 2bcde1c..2f6ded5 100644 --- a/arch/arm/mach-shmobile/pm_runtime.c +++ b/arch/arm/mach-shmobile/pm_runtime.c @@ -56,3 +56,13 @@ static int __init sh_pm_runtime_init(void) return 0; } core_initcall(sh_pm_runtime_init); + +void (*shmobile_runtime_pm_late_init)(void); + +static int __init sh_pm_runtime_late_init(void) +{ + if (shmobile_runtime_pm_late_init) + shmobile_runtime_pm_late_init(); + return 0; +} +late_initcall(sh_pm_runtime_late_init); -- cgit v0.10.2 From b9416f03fd9f809225a90f63b2fbc86f90010eab Mon Sep 17 00:00:00 2001 From: Magnus Damm Date: Sun, 10 Jul 2011 10:38:53 +0200 Subject: ARM: mach-shmobile: sh7372 late pm domain off Add sh7372 specific code to power down unused pm domains. This should really be replaced by some generic PM core code IMO, but until that happens this patch makes sure we don't waste power by leaving unused power domains on. Signed-off-by: Magnus Damm Signed-off-by: Rafael J. Wysocki diff --git a/arch/arm/mach-shmobile/pm-sh7372.c b/arch/arm/mach-shmobile/pm-sh7372.c index a8e0de1..9ad3b61 100644 --- a/arch/arm/mach-shmobile/pm-sh7372.c +++ b/arch/arm/mach-shmobile/pm-sh7372.c @@ -96,6 +96,17 @@ static bool pd_active_wakeup(struct device *dev) return true; } +static void sh7372_late_pm_domain_off(void) +{ + /* request power down of unused pm domains */ + queue_work(pm_wq, &sh7372_a4lc.genpd.power_off_work); + queue_work(pm_wq, &sh7372_a4mp.genpd.power_off_work); + queue_work(pm_wq, &sh7372_d4.genpd.power_off_work); + queue_work(pm_wq, &sh7372_a3rv.genpd.power_off_work); + queue_work(pm_wq, &sh7372_a3ri.genpd.power_off_work); + queue_work(pm_wq, &sh7372_a3sg.genpd.power_off_work); +} + void sh7372_init_pm_domain(struct sh7372_pm_domain *sh7372_pd) { struct generic_pm_domain *genpd = &sh7372_pd->genpd; @@ -107,6 +118,8 @@ void sh7372_init_pm_domain(struct sh7372_pm_domain *sh7372_pd) genpd->power_off = pd_power_down; genpd->power_on = pd_power_up; pd_power_up(&sh7372_pd->genpd); + + shmobile_runtime_pm_late_init = sh7372_late_pm_domain_off; } void sh7372_add_device_to_domain(struct sh7372_pm_domain *sh7372_pd, -- cgit v0.10.2 From 18b4f3f5d058b590e7189027eeb5d897742ade0a Mon Sep 17 00:00:00 2001 From: Magnus Damm Date: Sun, 10 Jul 2011 10:39:14 +0200 Subject: PM / Domains: Export pm_genpd_poweron() in header Allow SoC-specific code to call pm_genpd_poweron(). Signed-off-by: Magnus Damm Signed-off-by: Rafael J. Wysocki diff --git a/drivers/base/power/domain.c b/drivers/base/power/domain.c index 1aed94c..1f1a7d8 100644 --- a/drivers/base/power/domain.c +++ b/drivers/base/power/domain.c @@ -37,7 +37,7 @@ static void genpd_sd_counter_dec(struct generic_pm_domain *genpd) * Restore power to @genpd and all of its parents so that it is possible to * resume a device belonging to it. */ -static int pm_genpd_poweron(struct generic_pm_domain *genpd) +int pm_genpd_poweron(struct generic_pm_domain *genpd) { int ret = 0; diff --git a/include/linux/pm_domain.h b/include/linux/pm_domain.h index 98491ee..14fb095 100644 --- a/include/linux/pm_domain.h +++ b/include/linux/pm_domain.h @@ -63,6 +63,7 @@ extern int pm_genpd_remove_subdomain(struct generic_pm_domain *genpd, struct generic_pm_domain *target); extern void pm_genpd_init(struct generic_pm_domain *genpd, struct dev_power_governor *gov, bool is_off); +extern int pm_genpd_poweron(struct generic_pm_domain *genpd); #else static inline int pm_genpd_add_device(struct generic_pm_domain *genpd, struct device *dev) @@ -86,6 +87,10 @@ static inline int pm_genpd_remove_subdomain(struct generic_pm_domain *genpd, } static inline void pm_genpd_init(struct generic_pm_domain *genpd, struct dev_power_governor *gov, bool is_off) {} +static inline int pm_genpd_poweron(struct generic_pm_domain *genpd) +{ + return -ENOSYS; +} #endif #endif /* _LINUX_PM_DOMAIN_H */ -- cgit v0.10.2 From 775b8ae8707592af9275b8b216c2bf056b3f5d82 Mon Sep 17 00:00:00 2001 From: Magnus Damm Date: Sun, 10 Jul 2011 10:39:32 +0200 Subject: ARM: mach-shmobile: sh7372 A3RV requires A4LC Add a power domain workaround for the VPU and A3RV on sh7372. The sh7372 data sheet mentions that the VPU is located in the A3RV power domain. The A3RV power domain is not related to A4LC in any way, but testing shows that unless A3RV _and_ A4LC are powered on the VPU test program will bomb out. This issue may be caused by a more or less undocumented dependency on the MERAM block that happens to be located in A4LC. So now we know that the out-of-reset requirement of the VPU is that the MERAM is powered on. This patch adds a workaround for A3RV to make sure A4LC is powered on - this so we can use the VPU even though the LCDCs are in blanking state and A4LC is supposed to be off. Signed-off-by: Magnus Damm Signed-off-by: Rafael J. Wysocki diff --git a/arch/arm/mach-shmobile/pm-sh7372.c b/arch/arm/mach-shmobile/pm-sh7372.c index 9ad3b61..71400ea 100644 --- a/arch/arm/mach-shmobile/pm-sh7372.c +++ b/arch/arm/mach-shmobile/pm-sh7372.c @@ -91,6 +91,36 @@ static int pd_power_up(struct generic_pm_domain *genpd) return ret; } +static int pd_power_up_a3rv(struct generic_pm_domain *genpd) +{ + int ret = pd_power_up(genpd); + + /* force A4LC on after A3RV has been requested on */ + pm_genpd_poweron(&sh7372_a4lc.genpd); + + return ret; +} + +static int pd_power_down_a3rv(struct generic_pm_domain *genpd) +{ + int ret = pd_power_down(genpd); + + /* try to power down A4LC after A3RV is requested off */ + pm_genpd_poweron(&sh7372_a4lc.genpd); + queue_work(pm_wq, &sh7372_a4lc.genpd.power_off_work); + + return ret; +} + +static int pd_power_down_a4lc(struct generic_pm_domain *genpd) +{ + /* only power down A4LC if A3RV is off */ + if (!(__raw_readl(PSTR) & (1 << sh7372_a3rv.bit_shift))) + return pd_power_down(genpd); + + return 0; +} + static bool pd_active_wakeup(struct device *dev) { return true; @@ -115,9 +145,18 @@ void sh7372_init_pm_domain(struct sh7372_pm_domain *sh7372_pd) genpd->stop_device = pm_clk_suspend; genpd->start_device = pm_clk_resume; genpd->active_wakeup = pd_active_wakeup; - genpd->power_off = pd_power_down; - genpd->power_on = pd_power_up; - pd_power_up(&sh7372_pd->genpd); + + if (sh7372_pd == &sh7372_a4lc) { + genpd->power_off = pd_power_down_a4lc; + genpd->power_on = pd_power_up; + } else if (sh7372_pd == &sh7372_a3rv) { + genpd->power_off = pd_power_down_a3rv; + genpd->power_on = pd_power_up_a3rv; + } else { + genpd->power_off = pd_power_down; + genpd->power_on = pd_power_up; + } + genpd->power_on(&sh7372_pd->genpd); shmobile_runtime_pm_late_init = sh7372_late_pm_domain_off; } -- cgit v0.10.2 From 6f00ff78278fd5d6ac110b6903ee042af2d6af91 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Tue, 12 Jul 2011 00:39:10 +0200 Subject: PM / Domains: Set device state to "active" during system resume The runtime PM status of devices in a power domain that is not powered off in pm_genpd_complete() should be set to "active", because those devices are operational at this point. Some of them may not be in use, though, so make pm_genpd_complete() call pm_runtime_idle() in addition to pm_runtime_set_active() for each of them. Signed-off-by: Rafael J. Wysocki diff --git a/drivers/base/power/domain.c b/drivers/base/power/domain.c index 1f1a7d8..0e7e91b 100644 --- a/drivers/base/power/domain.c +++ b/drivers/base/power/domain.c @@ -786,7 +786,9 @@ static void pm_genpd_complete(struct device *dev) if (run_complete) { pm_generic_complete(dev); + pm_runtime_set_active(dev); pm_runtime_enable(dev); + pm_runtime_idle(dev); } } -- cgit v0.10.2 From b6c10c84665912985d0bf9b6ae8ce19fc4298d9f Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Tue, 12 Jul 2011 00:39:21 +0200 Subject: PM / Domains: Make failing pm_genpd_prepare() clean up properly If pm_generic_prepare() in pm_genpd_prepare() returns error code, the PM domains counter of "prepared" devices should be decremented and its suspend_power_off flag should be reset if this counter drops down to zero. Otherwise, the PM domain runtime PM code will not handle the domain correctly (it will permanently think that system suspend is in progress). Signed-off-by: Rafael J. Wysocki diff --git a/drivers/base/power/domain.c b/drivers/base/power/domain.c index 0e7e91b..9a20d93 100644 --- a/drivers/base/power/domain.c +++ b/drivers/base/power/domain.c @@ -367,6 +367,7 @@ static void pm_genpd_sync_poweroff(struct generic_pm_domain *genpd) static int pm_genpd_prepare(struct device *dev) { struct generic_pm_domain *genpd; + int ret; dev_dbg(dev, "%s()\n", __func__); @@ -400,7 +401,16 @@ static int pm_genpd_prepare(struct device *dev) mutex_unlock(&genpd->lock); - return pm_generic_prepare(dev); + ret = pm_generic_prepare(dev); + if (ret) { + mutex_lock(&genpd->lock); + + if (--genpd->prepared_count == 0) + genpd->suspend_power_off = false; + + mutex_unlock(&genpd->lock); + } + return ret; } /** -- cgit v0.10.2 From 17b75eca7683d4942f4d8d00563fd15f37c39589 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Tue, 12 Jul 2011 00:39:29 +0200 Subject: PM / Domains: Do not execute device callbacks under locks Currently, the .start_device() and .stop_device() callbacks from struct generic_pm_domain() as well as the device drivers' runtime PM callbacks used by the generic PM domains code are executed under the generic PM domain lock. This, unfortunately, is prone to deadlocks, for example if a device and its parent are boths members of the same PM domain. For this reason, it would be better if the PM domains code didn't execute device callbacks under the lock. Rework the locking in the generic PM domains code so that the lock is dropped for the execution of device callbacks. To this end, introduce PM domains states reflecting the current status of a PM domain and such that the PM domain lock cannot be acquired if the status is GPD_STATE_BUSY. Make threads attempting to acquire a PM domain's lock wait until the status changes to either GPD_STATE_ACTIVE or GPD_STATE_POWER_OFF. This change by itself doesn't fix the deadlock problem mentioned above, but the mechanism introduced by it will be used for for this purpose by a subsequent patch. Signed-off-by: Rafael J. Wysocki diff --git a/drivers/base/power/domain.c b/drivers/base/power/domain.c index 9a20d93..d06f3bb 100644 --- a/drivers/base/power/domain.c +++ b/drivers/base/power/domain.c @@ -13,6 +13,8 @@ #include #include #include +#include +#include #ifdef CONFIG_PM @@ -30,6 +32,34 @@ static void genpd_sd_counter_dec(struct generic_pm_domain *genpd) genpd->sd_count--; } +static void genpd_acquire_lock(struct generic_pm_domain *genpd) +{ + DEFINE_WAIT(wait); + + mutex_lock(&genpd->lock); + /* + * Wait for the domain to transition into either the active, + * or the power off state. + */ + for (;;) { + prepare_to_wait(&genpd->status_wait_queue, &wait, + TASK_UNINTERRUPTIBLE); + if (genpd->status != GPD_STATE_BUSY) + break; + mutex_unlock(&genpd->lock); + + schedule(); + + mutex_lock(&genpd->lock); + } + finish_wait(&genpd->status_wait_queue, &wait); +} + +static void genpd_release_lock(struct generic_pm_domain *genpd) +{ + mutex_unlock(&genpd->lock); +} + /** * pm_genpd_poweron - Restore power to a given PM domain and its parents. * @genpd: PM domain to power up. @@ -39,22 +69,50 @@ static void genpd_sd_counter_dec(struct generic_pm_domain *genpd) */ int pm_genpd_poweron(struct generic_pm_domain *genpd) { + struct generic_pm_domain *parent = genpd->parent; + DEFINE_WAIT(wait); int ret = 0; start: - if (genpd->parent) - mutex_lock(&genpd->parent->lock); - mutex_lock_nested(&genpd->lock, SINGLE_DEPTH_NESTING); + if (parent) { + mutex_lock(&parent->lock); + mutex_lock_nested(&genpd->lock, SINGLE_DEPTH_NESTING); + } else { + mutex_lock(&genpd->lock); + } + /* + * Wait for the domain to transition into either the active, + * or the power off state. + */ + for (;;) { + prepare_to_wait(&genpd->status_wait_queue, &wait, + TASK_UNINTERRUPTIBLE); + if (genpd->status != GPD_STATE_BUSY) + break; + mutex_unlock(&genpd->lock); + if (parent) + mutex_unlock(&parent->lock); + + schedule(); - if (!genpd->power_is_off + if (parent) { + mutex_lock(&parent->lock); + mutex_lock_nested(&genpd->lock, SINGLE_DEPTH_NESTING); + } else { + mutex_lock(&genpd->lock); + } + } + finish_wait(&genpd->status_wait_queue, &wait); + + if (genpd->status == GPD_STATE_ACTIVE || (genpd->prepared_count > 0 && genpd->suspend_power_off)) goto out; - if (genpd->parent && genpd->parent->power_is_off) { + if (parent && parent->status != GPD_STATE_ACTIVE) { mutex_unlock(&genpd->lock); - mutex_unlock(&genpd->parent->lock); + mutex_unlock(&parent->lock); - ret = pm_genpd_poweron(genpd->parent); + ret = pm_genpd_poweron(parent); if (ret) return ret; @@ -67,14 +125,14 @@ int pm_genpd_poweron(struct generic_pm_domain *genpd) goto out; } - genpd->power_is_off = false; - if (genpd->parent) - genpd->parent->sd_count++; + genpd->status = GPD_STATE_ACTIVE; + if (parent) + parent->sd_count++; out: mutex_unlock(&genpd->lock); - if (genpd->parent) - mutex_unlock(&genpd->parent->lock); + if (parent) + mutex_unlock(&parent->lock); return ret; } @@ -90,6 +148,7 @@ int pm_genpd_poweron(struct generic_pm_domain *genpd) */ static int __pm_genpd_save_device(struct dev_list_entry *dle, struct generic_pm_domain *genpd) + __releases(&genpd->lock) __acquires(&genpd->lock) { struct device *dev = dle->dev; struct device_driver *drv = dev->driver; @@ -98,6 +157,8 @@ static int __pm_genpd_save_device(struct dev_list_entry *dle, if (dle->need_restore) return 0; + mutex_unlock(&genpd->lock); + if (drv && drv->pm && drv->pm->runtime_suspend) { if (genpd->start_device) genpd->start_device(dev); @@ -108,6 +169,8 @@ static int __pm_genpd_save_device(struct dev_list_entry *dle, genpd->stop_device(dev); } + mutex_lock(&genpd->lock); + if (!ret) dle->need_restore = true; @@ -121,6 +184,7 @@ static int __pm_genpd_save_device(struct dev_list_entry *dle, */ static void __pm_genpd_restore_device(struct dev_list_entry *dle, struct generic_pm_domain *genpd) + __releases(&genpd->lock) __acquires(&genpd->lock) { struct device *dev = dle->dev; struct device_driver *drv = dev->driver; @@ -128,6 +192,8 @@ static void __pm_genpd_restore_device(struct dev_list_entry *dle, if (!dle->need_restore) return; + mutex_unlock(&genpd->lock); + if (drv && drv->pm && drv->pm->runtime_resume) { if (genpd->start_device) genpd->start_device(dev); @@ -138,6 +204,8 @@ static void __pm_genpd_restore_device(struct dev_list_entry *dle, genpd->stop_device(dev); } + mutex_lock(&genpd->lock); + dle->need_restore = false; } @@ -150,13 +218,14 @@ static void __pm_genpd_restore_device(struct dev_list_entry *dle, * the @genpd's devices' drivers and remove power from @genpd. */ static int pm_genpd_poweroff(struct generic_pm_domain *genpd) + __releases(&genpd->lock) __acquires(&genpd->lock) { struct generic_pm_domain *parent; struct dev_list_entry *dle; unsigned int not_suspended; int ret; - if (genpd->power_is_off || genpd->prepared_count > 0) + if (genpd->status == GPD_STATE_POWER_OFF || genpd->prepared_count > 0) return 0; if (genpd->sd_count > 0) @@ -175,22 +244,36 @@ static int pm_genpd_poweroff(struct generic_pm_domain *genpd) return -EAGAIN; } + genpd->status = GPD_STATE_BUSY; + list_for_each_entry_reverse(dle, &genpd->dev_list, node) { ret = __pm_genpd_save_device(dle, genpd); if (ret) goto err_dev; } + mutex_unlock(&genpd->lock); + + parent = genpd->parent; + if (parent) { + genpd_acquire_lock(parent); + mutex_lock_nested(&genpd->lock, SINGLE_DEPTH_NESTING); + } else { + mutex_lock(&genpd->lock); + } + if (genpd->power_off) genpd->power_off(genpd); - genpd->power_is_off = true; + genpd->status = GPD_STATE_POWER_OFF; + wake_up_all(&genpd->status_wait_queue); - parent = genpd->parent; if (parent) { genpd_sd_counter_dec(parent); if (parent->sd_count == 0) queue_work(pm_wq, &parent->power_off_work); + + genpd_release_lock(parent); } return 0; @@ -199,6 +282,9 @@ static int pm_genpd_poweroff(struct generic_pm_domain *genpd) list_for_each_entry_continue(dle, &genpd->dev_list, node) __pm_genpd_restore_device(dle, genpd); + genpd->status = GPD_STATE_ACTIVE; + wake_up_all(&genpd->status_wait_queue); + return ret; } @@ -212,13 +298,9 @@ static void genpd_power_off_work_fn(struct work_struct *work) genpd = container_of(work, struct generic_pm_domain, power_off_work); - if (genpd->parent) - mutex_lock(&genpd->parent->lock); - mutex_lock_nested(&genpd->lock, SINGLE_DEPTH_NESTING); + genpd_acquire_lock(genpd); pm_genpd_poweroff(genpd); - mutex_unlock(&genpd->lock); - if (genpd->parent) - mutex_unlock(&genpd->parent->lock); + genpd_release_lock(genpd); } /** @@ -239,23 +321,17 @@ static int pm_genpd_runtime_suspend(struct device *dev) if (IS_ERR(genpd)) return -EINVAL; - if (genpd->parent) - mutex_lock(&genpd->parent->lock); - mutex_lock_nested(&genpd->lock, SINGLE_DEPTH_NESTING); - if (genpd->stop_device) { int ret = genpd->stop_device(dev); if (ret) - goto out; + return ret; } + + genpd_acquire_lock(genpd); genpd->in_progress++; pm_genpd_poweroff(genpd); genpd->in_progress--; - - out: - mutex_unlock(&genpd->lock); - if (genpd->parent) - mutex_unlock(&genpd->parent->lock); + genpd_release_lock(genpd); return 0; } @@ -276,9 +352,6 @@ static void __pm_genpd_runtime_resume(struct device *dev, break; } } - - if (genpd->start_device) - genpd->start_device(dev); } /** @@ -304,9 +377,15 @@ static int pm_genpd_runtime_resume(struct device *dev) if (ret) return ret; - mutex_lock(&genpd->lock); + genpd_acquire_lock(genpd); + genpd->status = GPD_STATE_BUSY; __pm_genpd_runtime_resume(dev, genpd); - mutex_unlock(&genpd->lock); + genpd->status = GPD_STATE_ACTIVE; + wake_up_all(&genpd->status_wait_queue); + genpd_release_lock(genpd); + + if (genpd->start_device) + genpd->start_device(dev); return 0; } @@ -339,7 +418,7 @@ static void pm_genpd_sync_poweroff(struct generic_pm_domain *genpd) { struct generic_pm_domain *parent = genpd->parent; - if (genpd->power_is_off) + if (genpd->status == GPD_STATE_POWER_OFF) return; if (genpd->suspended_count != genpd->device_count || genpd->sd_count > 0) @@ -348,7 +427,7 @@ static void pm_genpd_sync_poweroff(struct generic_pm_domain *genpd) if (genpd->power_off) genpd->power_off(genpd); - genpd->power_is_off = true; + genpd->status = GPD_STATE_POWER_OFF; if (parent) { genpd_sd_counter_dec(parent); pm_genpd_sync_poweroff(parent); @@ -375,32 +454,41 @@ static int pm_genpd_prepare(struct device *dev) if (IS_ERR(genpd)) return -EINVAL; - mutex_lock(&genpd->lock); + /* + * If a wakeup request is pending for the device, it should be woken up + * at this point and a system wakeup event should be reported if it's + * set up to wake up the system from sleep states. + */ + pm_runtime_get_noresume(dev); + if (pm_runtime_barrier(dev) && device_may_wakeup(dev)) + pm_wakeup_event(dev, 0); + + if (pm_wakeup_pending()) { + pm_runtime_put_sync(dev); + return -EBUSY; + } + + genpd_acquire_lock(genpd); if (genpd->prepared_count++ == 0) - genpd->suspend_power_off = genpd->power_is_off; + genpd->suspend_power_off = genpd->status == GPD_STATE_POWER_OFF; + + genpd_release_lock(genpd); if (genpd->suspend_power_off) { - mutex_unlock(&genpd->lock); + pm_runtime_put_noidle(dev); return 0; } /* - * If the device is in the (runtime) "suspended" state, call - * .start_device() for it, if defined. - */ - if (pm_runtime_suspended(dev)) - __pm_genpd_runtime_resume(dev, genpd); - - /* - * Do not check if runtime resume is pending at this point, because it - * has been taken care of already and if pm_genpd_poweron() ran at this - * point as a result of the check, it would deadlock. + * The PM domain must be in the GPD_STATE_ACTIVE state at this point, + * so pm_genpd_poweron() will return immediately, but if the device + * is suspended (e.g. it's been stopped by .stop_device()), we need + * to make it operational. */ + pm_runtime_resume(dev); __pm_runtime_disable(dev, false); - mutex_unlock(&genpd->lock); - ret = pm_generic_prepare(dev); if (ret) { mutex_lock(&genpd->lock); @@ -409,7 +497,10 @@ static int pm_genpd_prepare(struct device *dev) genpd->suspend_power_off = false; mutex_unlock(&genpd->lock); + pm_runtime_enable(dev); } + + pm_runtime_put_sync(dev); return ret; } @@ -726,7 +817,7 @@ static int pm_genpd_restore_noirq(struct device *dev) * guaranteed that this function will never run twice in parallel for * the same PM domain, so it is not necessary to use locking here. */ - genpd->power_is_off = true; + genpd->status = GPD_STATE_POWER_OFF; if (genpd->suspend_power_off) { /* * The boot kernel might put the domain into the power on state, @@ -836,9 +927,9 @@ int pm_genpd_add_device(struct generic_pm_domain *genpd, struct device *dev) if (IS_ERR_OR_NULL(genpd) || IS_ERR_OR_NULL(dev)) return -EINVAL; - mutex_lock(&genpd->lock); + genpd_acquire_lock(genpd); - if (genpd->power_is_off) { + if (genpd->status == GPD_STATE_POWER_OFF) { ret = -EINVAL; goto out; } @@ -870,7 +961,7 @@ int pm_genpd_add_device(struct generic_pm_domain *genpd, struct device *dev) spin_unlock_irq(&dev->power.lock); out: - mutex_unlock(&genpd->lock); + genpd_release_lock(genpd); return ret; } @@ -891,7 +982,7 @@ int pm_genpd_remove_device(struct generic_pm_domain *genpd, if (IS_ERR_OR_NULL(genpd) || IS_ERR_OR_NULL(dev)) return -EINVAL; - mutex_lock(&genpd->lock); + genpd_acquire_lock(genpd); if (genpd->prepared_count > 0) { ret = -EAGAIN; @@ -915,7 +1006,7 @@ int pm_genpd_remove_device(struct generic_pm_domain *genpd, } out: - mutex_unlock(&genpd->lock); + genpd_release_lock(genpd); return ret; } @@ -934,9 +1025,19 @@ int pm_genpd_add_subdomain(struct generic_pm_domain *genpd, if (IS_ERR_OR_NULL(genpd) || IS_ERR_OR_NULL(new_subdomain)) return -EINVAL; - mutex_lock(&genpd->lock); + start: + genpd_acquire_lock(genpd); + mutex_lock_nested(&new_subdomain->lock, SINGLE_DEPTH_NESTING); - if (genpd->power_is_off && !new_subdomain->power_is_off) { + if (new_subdomain->status != GPD_STATE_POWER_OFF + && new_subdomain->status != GPD_STATE_ACTIVE) { + mutex_unlock(&new_subdomain->lock); + genpd_release_lock(genpd); + goto start; + } + + if (genpd->status == GPD_STATE_POWER_OFF + && new_subdomain->status != GPD_STATE_POWER_OFF) { ret = -EINVAL; goto out; } @@ -948,17 +1049,14 @@ int pm_genpd_add_subdomain(struct generic_pm_domain *genpd, } } - mutex_lock_nested(&new_subdomain->lock, SINGLE_DEPTH_NESTING); - list_add_tail(&new_subdomain->sd_node, &genpd->sd_list); new_subdomain->parent = genpd; - if (!subdomain->power_is_off) + if (subdomain->status != GPD_STATE_POWER_OFF) genpd->sd_count++; - mutex_unlock(&new_subdomain->lock); - out: - mutex_unlock(&genpd->lock); + mutex_unlock(&new_subdomain->lock); + genpd_release_lock(genpd); return ret; } @@ -977,7 +1075,8 @@ int pm_genpd_remove_subdomain(struct generic_pm_domain *genpd, if (IS_ERR_OR_NULL(genpd) || IS_ERR_OR_NULL(target)) return -EINVAL; - mutex_lock(&genpd->lock); + start: + genpd_acquire_lock(genpd); list_for_each_entry(subdomain, &genpd->sd_list, sd_node) { if (subdomain != target) @@ -985,9 +1084,16 @@ int pm_genpd_remove_subdomain(struct generic_pm_domain *genpd, mutex_lock_nested(&subdomain->lock, SINGLE_DEPTH_NESTING); + if (subdomain->status != GPD_STATE_POWER_OFF + && subdomain->status != GPD_STATE_ACTIVE) { + mutex_unlock(&subdomain->lock); + genpd_release_lock(genpd); + goto start; + } + list_del(&subdomain->sd_node); subdomain->parent = NULL; - if (!subdomain->power_is_off) + if (subdomain->status != GPD_STATE_POWER_OFF) genpd_sd_counter_dec(genpd); mutex_unlock(&subdomain->lock); @@ -996,7 +1102,7 @@ int pm_genpd_remove_subdomain(struct generic_pm_domain *genpd, break; } - mutex_unlock(&genpd->lock); + genpd_release_lock(genpd); return ret; } @@ -1022,7 +1128,8 @@ void pm_genpd_init(struct generic_pm_domain *genpd, INIT_WORK(&genpd->power_off_work, genpd_power_off_work_fn); genpd->in_progress = 0; genpd->sd_count = 0; - genpd->power_is_off = is_off; + genpd->status = is_off ? GPD_STATE_POWER_OFF : GPD_STATE_ACTIVE; + init_waitqueue_head(&genpd->status_wait_queue); genpd->device_count = 0; genpd->suspended_count = 0; genpd->domain.ops.runtime_suspend = pm_genpd_runtime_suspend; diff --git a/include/linux/pm_domain.h b/include/linux/pm_domain.h index 14fb095..c71457c 100644 --- a/include/linux/pm_domain.h +++ b/include/linux/pm_domain.h @@ -11,8 +11,11 @@ #include -#define GPD_IN_SUSPEND 1 -#define GPD_POWER_OFF 2 +enum gpd_status { + GPD_STATE_ACTIVE = 0, /* PM domain is active */ + GPD_STATE_BUSY, /* Something is happening to the PM domain */ + GPD_STATE_POWER_OFF, /* PM domain is off */ +}; struct dev_power_governor { bool (*power_down_ok)(struct dev_pm_domain *domain); @@ -29,7 +32,8 @@ struct generic_pm_domain { struct work_struct power_off_work; unsigned int in_progress; /* Number of devices being suspended now */ unsigned int sd_count; /* Number of subdomains with power "on" */ - bool power_is_off; /* Whether or not power has been removed */ + enum gpd_status status; /* Current state of the domain */ + wait_queue_head_t status_wait_queue; unsigned int device_count; /* Number of devices */ unsigned int suspended_count; /* System suspend device counter */ unsigned int prepared_count; /* Suspend counter of prepared devices */ -- cgit v0.10.2 From c6d22b37263607ba5aeeb2e11169fa65caa29bee Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Tue, 12 Jul 2011 00:39:36 +0200 Subject: PM / Domains: Allow callbacks to execute all runtime PM helpers A deadlock may occur if one of the PM domains' .start_device() or .stop_device() callbacks or a device driver's .runtime_suspend() or .runtime_resume() callback executed by the core generic PM domain code uses a "wrong" runtime PM helper function. This happens, for example, if .runtime_resume() from one device's driver calls pm_runtime_resume() for another device in the same PM domain. A similar situation may take place if a device's parent is in the same PM domain, in which case the runtime PM framework may execute pm_genpd_runtime_resume() automatically for the parent (if it is suspended at the moment). This, of course, is undesirable, so the generic PM domains code should be modified to prevent it from happening. The runtime PM framework guarantees that pm_genpd_runtime_suspend() and pm_genpd_runtime_resume() won't be executed in parallel for the same device, so the generic PM domains code need not worry about those cases. Still, it needs to prevent the other possible race conditions between pm_genpd_runtime_suspend(), pm_genpd_runtime_resume(), pm_genpd_poweron() and pm_genpd_poweroff() from happening and it needs to avoid deadlocks at the same time. To this end, modify the generic PM domains code to relax synchronization rules so that: * pm_genpd_poweron() doesn't wait for the PM domain status to change from GPD_STATE_BUSY. If it finds that the status is not GPD_STATE_POWER_OFF, it returns without powering the domain on (it may modify the status depending on the circumstances). * pm_genpd_poweroff() returns as soon as it finds that the PM domain's status changed from GPD_STATE_BUSY after it's released the PM domain's lock. * pm_genpd_runtime_suspend() doesn't wait for the PM domain status to change from GPD_STATE_BUSY after executing the domain's .stop_device() callback and executes pm_genpd_poweroff() only if pm_genpd_runtime_resume() is not executed in parallel. * pm_genpd_runtime_resume() doesn't wait for the PM domain status to change from GPD_STATE_BUSY after executing pm_genpd_poweron() and sets the domain's status to GPD_STATE_BUSY and increments its counter of resuming devices (introduced by this change) immediately after acquiring the lock. The counter of resuming devices is then decremented after executing __pm_genpd_runtime_resume() for the device and the domain's status is reset to GPD_STATE_ACTIVE (unless there are more resuming devices in the domain, in which case the status remains GPD_STATE_BUSY). This way, for example, if a device driver's .runtime_resume() callback executes pm_runtime_resume() for another device in the same PM domain, pm_genpd_poweron() called by pm_genpd_runtime_resume() invoked by the runtime PM framework will not block and it will see that there's nothing to do for it. Next, the PM domain's lock will be acquired without waiting for its status to change from GPD_STATE_BUSY and the device driver's .runtime_resume() callback will be executed. In turn, if pm_runtime_suspend() is executed by one device driver's .runtime_resume() callback for another device in the same PM domain, pm_genpd_poweroff() executed by pm_genpd_runtime_suspend() invoked by the runtime PM framework as a result will notice that one of the devices in the domain is being resumed, so it will return immediately. Signed-off-by: Rafael J. Wysocki diff --git a/drivers/base/power/domain.c b/drivers/base/power/domain.c index d06f3bb..7e6cc8a 100644 --- a/drivers/base/power/domain.c +++ b/drivers/base/power/domain.c @@ -44,7 +44,8 @@ static void genpd_acquire_lock(struct generic_pm_domain *genpd) for (;;) { prepare_to_wait(&genpd->status_wait_queue, &wait, TASK_UNINTERRUPTIBLE); - if (genpd->status != GPD_STATE_BUSY) + if (genpd->status == GPD_STATE_ACTIVE + || genpd->status == GPD_STATE_POWER_OFF) break; mutex_unlock(&genpd->lock); @@ -60,6 +61,12 @@ static void genpd_release_lock(struct generic_pm_domain *genpd) mutex_unlock(&genpd->lock); } +static void genpd_set_active(struct generic_pm_domain *genpd) +{ + if (genpd->resume_count == 0) + genpd->status = GPD_STATE_ACTIVE; +} + /** * pm_genpd_poweron - Restore power to a given PM domain and its parents. * @genpd: PM domain to power up. @@ -75,42 +82,24 @@ int pm_genpd_poweron(struct generic_pm_domain *genpd) start: if (parent) { - mutex_lock(&parent->lock); + genpd_acquire_lock(parent); mutex_lock_nested(&genpd->lock, SINGLE_DEPTH_NESTING); } else { mutex_lock(&genpd->lock); } - /* - * Wait for the domain to transition into either the active, - * or the power off state. - */ - for (;;) { - prepare_to_wait(&genpd->status_wait_queue, &wait, - TASK_UNINTERRUPTIBLE); - if (genpd->status != GPD_STATE_BUSY) - break; - mutex_unlock(&genpd->lock); - if (parent) - mutex_unlock(&parent->lock); - - schedule(); - - if (parent) { - mutex_lock(&parent->lock); - mutex_lock_nested(&genpd->lock, SINGLE_DEPTH_NESTING); - } else { - mutex_lock(&genpd->lock); - } - } - finish_wait(&genpd->status_wait_queue, &wait); if (genpd->status == GPD_STATE_ACTIVE || (genpd->prepared_count > 0 && genpd->suspend_power_off)) goto out; + if (genpd->status != GPD_STATE_POWER_OFF) { + genpd_set_active(genpd); + goto out; + } + if (parent && parent->status != GPD_STATE_ACTIVE) { mutex_unlock(&genpd->lock); - mutex_unlock(&parent->lock); + genpd_release_lock(parent); ret = pm_genpd_poweron(parent); if (ret) @@ -125,14 +114,14 @@ int pm_genpd_poweron(struct generic_pm_domain *genpd) goto out; } - genpd->status = GPD_STATE_ACTIVE; + genpd_set_active(genpd); if (parent) parent->sd_count++; out: mutex_unlock(&genpd->lock); if (parent) - mutex_unlock(&parent->lock); + genpd_release_lock(parent); return ret; } @@ -210,6 +199,20 @@ static void __pm_genpd_restore_device(struct dev_list_entry *dle, } /** + * genpd_abort_poweroff - Check if a PM domain power off should be aborted. + * @genpd: PM domain to check. + * + * Return true if a PM domain's status changed to GPD_STATE_ACTIVE during + * a "power off" operation, which means that a "power on" has occured in the + * meantime, or if its resume_count field is different from zero, which means + * that one of its devices has been resumed in the meantime. + */ +static bool genpd_abort_poweroff(struct generic_pm_domain *genpd) +{ + return genpd->status == GPD_STATE_ACTIVE || genpd->resume_count > 0; +} + +/** * pm_genpd_poweroff - Remove power from a given PM domain. * @genpd: PM domain to power down. * @@ -223,9 +226,17 @@ static int pm_genpd_poweroff(struct generic_pm_domain *genpd) struct generic_pm_domain *parent; struct dev_list_entry *dle; unsigned int not_suspended; - int ret; + int ret = 0; - if (genpd->status == GPD_STATE_POWER_OFF || genpd->prepared_count > 0) + start: + /* + * Do not try to power off the domain in the following situations: + * (1) The domain is already in the "power off" state. + * (2) System suspend is in progress. + * (3) One of the domain's devices is being resumed right now. + */ + if (genpd->status == GPD_STATE_POWER_OFF || genpd->prepared_count > 0 + || genpd->resume_count > 0) return 0; if (genpd->sd_count > 0) @@ -239,34 +250,54 @@ static int pm_genpd_poweroff(struct generic_pm_domain *genpd) if (not_suspended > genpd->in_progress) return -EBUSY; + if (genpd->poweroff_task) { + /* + * Another instance of pm_genpd_poweroff() is executing + * callbacks, so tell it to start over and return. + */ + genpd->status = GPD_STATE_REPEAT; + return 0; + } + if (genpd->gov && genpd->gov->power_down_ok) { if (!genpd->gov->power_down_ok(&genpd->domain)) return -EAGAIN; } genpd->status = GPD_STATE_BUSY; + genpd->poweroff_task = current; list_for_each_entry_reverse(dle, &genpd->dev_list, node) { ret = __pm_genpd_save_device(dle, genpd); if (ret) goto err_dev; - } - mutex_unlock(&genpd->lock); + if (genpd_abort_poweroff(genpd)) + goto out; + + if (genpd->status == GPD_STATE_REPEAT) { + genpd->poweroff_task = NULL; + goto start; + } + } parent = genpd->parent; if (parent) { + mutex_unlock(&genpd->lock); + genpd_acquire_lock(parent); mutex_lock_nested(&genpd->lock, SINGLE_DEPTH_NESTING); - } else { - mutex_lock(&genpd->lock); + + if (genpd_abort_poweroff(genpd)) { + genpd_release_lock(parent); + goto out; + } } if (genpd->power_off) genpd->power_off(genpd); genpd->status = GPD_STATE_POWER_OFF; - wake_up_all(&genpd->status_wait_queue); if (parent) { genpd_sd_counter_dec(parent); @@ -276,16 +307,17 @@ static int pm_genpd_poweroff(struct generic_pm_domain *genpd) genpd_release_lock(parent); } - return 0; + out: + genpd->poweroff_task = NULL; + wake_up_all(&genpd->status_wait_queue); + return ret; err_dev: list_for_each_entry_continue(dle, &genpd->dev_list, node) __pm_genpd_restore_device(dle, genpd); - genpd->status = GPD_STATE_ACTIVE; - wake_up_all(&genpd->status_wait_queue); - - return ret; + genpd_set_active(genpd); + goto out; } /** @@ -327,11 +359,11 @@ static int pm_genpd_runtime_suspend(struct device *dev) return ret; } - genpd_acquire_lock(genpd); + mutex_lock(&genpd->lock); genpd->in_progress++; pm_genpd_poweroff(genpd); genpd->in_progress--; - genpd_release_lock(genpd); + mutex_unlock(&genpd->lock); return 0; } @@ -365,6 +397,7 @@ static void __pm_genpd_runtime_resume(struct device *dev, static int pm_genpd_runtime_resume(struct device *dev) { struct generic_pm_domain *genpd; + DEFINE_WAIT(wait); int ret; dev_dbg(dev, "%s()\n", __func__); @@ -377,12 +410,31 @@ static int pm_genpd_runtime_resume(struct device *dev) if (ret) return ret; - genpd_acquire_lock(genpd); + mutex_lock(&genpd->lock); genpd->status = GPD_STATE_BUSY; + genpd->resume_count++; + for (;;) { + prepare_to_wait(&genpd->status_wait_queue, &wait, + TASK_UNINTERRUPTIBLE); + /* + * If current is the powering off task, we have been called + * reentrantly from one of the device callbacks, so we should + * not wait. + */ + if (!genpd->poweroff_task || genpd->poweroff_task == current) + break; + mutex_unlock(&genpd->lock); + + schedule(); + + mutex_lock(&genpd->lock); + } + finish_wait(&genpd->status_wait_queue, &wait); __pm_genpd_runtime_resume(dev, genpd); - genpd->status = GPD_STATE_ACTIVE; + genpd->resume_count--; + genpd_set_active(genpd); wake_up_all(&genpd->status_wait_queue); - genpd_release_lock(genpd); + mutex_unlock(&genpd->lock); if (genpd->start_device) genpd->start_device(dev); @@ -1130,6 +1182,8 @@ void pm_genpd_init(struct generic_pm_domain *genpd, genpd->sd_count = 0; genpd->status = is_off ? GPD_STATE_POWER_OFF : GPD_STATE_ACTIVE; init_waitqueue_head(&genpd->status_wait_queue); + genpd->poweroff_task = NULL; + genpd->resume_count = 0; genpd->device_count = 0; genpd->suspended_count = 0; genpd->domain.ops.runtime_suspend = pm_genpd_runtime_suspend; diff --git a/include/linux/pm_domain.h b/include/linux/pm_domain.h index c71457c..feb80af 100644 --- a/include/linux/pm_domain.h +++ b/include/linux/pm_domain.h @@ -14,6 +14,7 @@ enum gpd_status { GPD_STATE_ACTIVE = 0, /* PM domain is active */ GPD_STATE_BUSY, /* Something is happening to the PM domain */ + GPD_STATE_REPEAT, /* Power off in progress, to be repeated */ GPD_STATE_POWER_OFF, /* PM domain is off */ }; @@ -34,6 +35,8 @@ struct generic_pm_domain { unsigned int sd_count; /* Number of subdomains with power "on" */ enum gpd_status status; /* Current state of the domain */ wait_queue_head_t status_wait_queue; + struct task_struct *poweroff_task; /* Powering off task */ + unsigned int resume_count; /* Number of devices being resumed */ unsigned int device_count; /* Number of devices */ unsigned int suspended_count; /* System suspend device counter */ unsigned int prepared_count; /* Suspend counter of prepared devices */ -- cgit v0.10.2 From 697a7f3727b53c7d4c927948bbe1f6afc4fabfde Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Tue, 12 Jul 2011 00:39:48 +0200 Subject: PM / Domains: Do not restore all devices on power off error Since every device in a PM domain has its own need_restore flag, which is set by __pm_genpd_save_device(), there's no need to walk the domain's device list and restore all devices on an error from one of the drivers' .runtime_suspend() callbacks. Signed-off-by: Rafael J. Wysocki diff --git a/drivers/base/power/domain.c b/drivers/base/power/domain.c index 7e6cc8a..7b20801 100644 --- a/drivers/base/power/domain.c +++ b/drivers/base/power/domain.c @@ -269,8 +269,10 @@ static int pm_genpd_poweroff(struct generic_pm_domain *genpd) list_for_each_entry_reverse(dle, &genpd->dev_list, node) { ret = __pm_genpd_save_device(dle, genpd); - if (ret) - goto err_dev; + if (ret) { + genpd_set_active(genpd); + goto out; + } if (genpd_abort_poweroff(genpd)) goto out; @@ -311,13 +313,6 @@ static int pm_genpd_poweroff(struct generic_pm_domain *genpd) genpd->poweroff_task = NULL; wake_up_all(&genpd->status_wait_queue); return ret; - - err_dev: - list_for_each_entry_continue(dle, &genpd->dev_list, node) - __pm_genpd_restore_device(dle, genpd); - - genpd_set_active(genpd); - goto out; } /** -- cgit v0.10.2 From 4ecd6e651dd25ebbf0cc53c68162c0ab08641725 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Tue, 12 Jul 2011 00:39:57 +0200 Subject: PM / Domains: Improve handling of wakeup devices during system suspend Kevin points out that if there's a device that can wake up the system from sleep states, but it doesn't generate wakeup signals by itself (they are generated on its behalf by other parts of the system) and it currently is not enabled to wake up the system (that is, device_may_wakeup() returns "false" for it), we may need to change its wakeup settings during system suspend (for example, the device might have been configured to signal remote wakeup from the system's working state, as needed by runtime PM). Therefore the generic PM domains code should invoke the system suspend callbacks provided by the device's driver, which it doesn't do if the PM domain is powered off during the system suspend's "prepare" stage. This is a valid point. Moreover, this code also should make sure that system wakeup devices that are enabled to wake up the system from sleep states and have to remain active for this purpose are not suspended while the system is in a sleep state. To avoid the above issues, make the generic PM domains' .prepare() routine, pm_genpd_prepare(), force runtime resume of devices whose system wakeup settings may need to be changed during system suspend or that should remain active while the system is in a sleep state to be able to wake it up from that state. Reported-by: Kevin Hilman Signed-off-by: Rafael J. Wysocki diff --git a/drivers/base/power/domain.c b/drivers/base/power/domain.c index 7b20801..b6e29ff 100644 --- a/drivers/base/power/domain.c +++ b/drivers/base/power/domain.c @@ -482,6 +482,33 @@ static void pm_genpd_sync_poweroff(struct generic_pm_domain *genpd) } /** + * resume_needed - Check whether to resume a device before system suspend. + * @dev: Device to check. + * @genpd: PM domain the device belongs to. + * + * There are two cases in which a device that can wake up the system from sleep + * states should be resumed by pm_genpd_prepare(): (1) if the device is enabled + * to wake up the system and it has to remain active for this purpose while the + * system is in the sleep state and (2) if the device is not enabled to wake up + * the system from sleep states and it generally doesn't generate wakeup signals + * by itself (those signals are generated on its behalf by other parts of the + * system). In the latter case it may be necessary to reconfigure the device's + * wakeup settings during system suspend, because it may have been set up to + * signal remote wakeup from the system's working state as needed by runtime PM. + * Return 'true' in either of the above cases. + */ +static bool resume_needed(struct device *dev, struct generic_pm_domain *genpd) +{ + bool active_wakeup; + + if (!device_can_wakeup(dev)) + return false; + + active_wakeup = genpd->active_wakeup && genpd->active_wakeup(dev); + return device_may_wakeup(dev) ? active_wakeup : !active_wakeup; +} + +/** * pm_genpd_prepare - Start power transition of a device in a PM domain. * @dev: Device to start the transition of. * @@ -515,6 +542,9 @@ static int pm_genpd_prepare(struct device *dev) return -EBUSY; } + if (resume_needed(dev, genpd)) + pm_runtime_resume(dev); + genpd_acquire_lock(genpd); if (genpd->prepared_count++ == 0) -- cgit v0.10.2 From 56375fd420f851944960bd53dbb08d674f4d9406 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Tue, 12 Jul 2011 00:40:03 +0200 Subject: PM / Domains: Queue up power off work only if it is not pending In theory it is possible that pm_genpd_poweroff() for two different subdomains of the same parent domain will attempt to queue up the execution of pm_genpd_poweroff() for the parent twice in a row. This would lead to unpleasant consequences, so prevent it from happening by checking if genpd->power_off_work is pending before attempting to queue it up. Signed-off-by: Rafael J. Wysocki diff --git a/drivers/base/power/domain.c b/drivers/base/power/domain.c index b6e29ff..c3e4e29 100644 --- a/drivers/base/power/domain.c +++ b/drivers/base/power/domain.c @@ -213,6 +213,19 @@ static bool genpd_abort_poweroff(struct generic_pm_domain *genpd) } /** + * genpd_queue_power_off_work - Queue up the execution of pm_genpd_poweroff(). + * @genpd: PM domait to power off. + * + * Queue up the execution of pm_genpd_poweroff() unless it's already been done + * before. + */ +static void genpd_queue_power_off_work(struct generic_pm_domain *genpd) +{ + if (!work_pending(&genpd->power_off_work)) + queue_work(pm_wq, &genpd->power_off_work); +} + +/** * pm_genpd_poweroff - Remove power from a given PM domain. * @genpd: PM domain to power down. * @@ -304,7 +317,7 @@ static int pm_genpd_poweroff(struct generic_pm_domain *genpd) if (parent) { genpd_sd_counter_dec(parent); if (parent->sd_count == 0) - queue_work(pm_wq, &parent->power_off_work); + genpd_queue_power_off_work(parent); genpd_release_lock(parent); } -- cgit v0.10.2 From 5125bbf3880755419eff68672623cde49c4f31e8 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Wed, 13 Jul 2011 12:31:52 +0200 Subject: PM / Domains: Introduce function to power off all unused PM domains Add a new function pm_genpd_poweroff_unused() queuing up the execution of pm_genpd_poweroff() for every initialized generic PM domain. Calling it will cause every generic PM domain without devices in use to be powered off. Signed-off-by: Rafael J. Wysocki Acked-by: Magnus Damm diff --git a/drivers/base/power/domain.c b/drivers/base/power/domain.c index c3e4e29..c2c537d 100644 --- a/drivers/base/power/domain.c +++ b/drivers/base/power/domain.c @@ -16,6 +16,9 @@ #include #include +static LIST_HEAD(gpd_list); +static DEFINE_MUTEX(gpd_list_lock); + #ifdef CONFIG_PM static struct generic_pm_domain *dev_to_genpd(struct device *dev) @@ -1241,4 +1244,22 @@ void pm_genpd_init(struct generic_pm_domain *genpd, genpd->domain.ops.restore_noirq = pm_genpd_restore_noirq; genpd->domain.ops.restore = pm_genpd_restore; genpd->domain.ops.complete = pm_genpd_complete; + mutex_lock(&gpd_list_lock); + list_add(&genpd->gpd_list_node, &gpd_list); + mutex_unlock(&gpd_list_lock); +} + +/** + * pm_genpd_poweroff_unused - Power off all PM domains with no devices in use. + */ +void pm_genpd_poweroff_unused(void) +{ + struct generic_pm_domain *genpd; + + mutex_lock(&gpd_list_lock); + + list_for_each_entry(genpd, &gpd_list, gpd_list_node) + genpd_queue_power_off_work(genpd); + + mutex_unlock(&gpd_list_lock); } diff --git a/include/linux/pm_domain.h b/include/linux/pm_domain.h index feb80af..3e4f3d3 100644 --- a/include/linux/pm_domain.h +++ b/include/linux/pm_domain.h @@ -24,6 +24,7 @@ struct dev_power_governor { struct generic_pm_domain { struct dev_pm_domain domain; /* PM domain operations */ + struct list_head gpd_list_node; /* Node in the global PM domains list */ struct list_head sd_node; /* Node in the parent's subdomain list */ struct generic_pm_domain *parent; /* Parent PM domain */ struct list_head sd_list; /* List of dubdomains */ @@ -71,6 +72,7 @@ extern int pm_genpd_remove_subdomain(struct generic_pm_domain *genpd, extern void pm_genpd_init(struct generic_pm_domain *genpd, struct dev_power_governor *gov, bool is_off); extern int pm_genpd_poweron(struct generic_pm_domain *genpd); +extern void pm_genpd_poweroff_unused(void); #else static inline int pm_genpd_add_device(struct generic_pm_domain *genpd, struct device *dev) @@ -98,6 +100,7 @@ static inline int pm_genpd_poweron(struct generic_pm_domain *genpd) { return -ENOSYS; } +static inline void pm_genpd_poweroff_unused(void) {} #endif #endif /* _LINUX_PM_DOMAIN_H */ -- cgit v0.10.2 From 796204142a98b6e0e71b494e808d1b6ee62cc75f Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Wed, 13 Jul 2011 12:32:07 +0200 Subject: ARM / shmobile: Use pm_genpd_poweroff_unused() Make shmobile use pm_genpd_poweroff_unused() instead of the open-coded powering off PM domains without devices in use. Signed-off-by: Rafael J. Wysocki Acked-by: Magnus Damm diff --git a/arch/arm/mach-shmobile/include/mach/common.h b/arch/arm/mach-shmobile/include/mach/common.h index 73a76d7..06aecb3 100644 --- a/arch/arm/mach-shmobile/include/mach/common.h +++ b/arch/arm/mach-shmobile/include/mach/common.h @@ -12,7 +12,6 @@ extern struct platform_suspend_ops shmobile_suspend_ops; struct cpuidle_device; extern void (*shmobile_cpuidle_modes[])(void); extern void (*shmobile_cpuidle_setup)(struct cpuidle_device *dev); -extern void (*shmobile_runtime_pm_late_init)(void); extern void sh7367_init_irq(void); extern void sh7367_add_early_devices(void); diff --git a/arch/arm/mach-shmobile/pm-sh7372.c b/arch/arm/mach-shmobile/pm-sh7372.c index 71400ea..f47281a 100644 --- a/arch/arm/mach-shmobile/pm-sh7372.c +++ b/arch/arm/mach-shmobile/pm-sh7372.c @@ -126,17 +126,6 @@ static bool pd_active_wakeup(struct device *dev) return true; } -static void sh7372_late_pm_domain_off(void) -{ - /* request power down of unused pm domains */ - queue_work(pm_wq, &sh7372_a4lc.genpd.power_off_work); - queue_work(pm_wq, &sh7372_a4mp.genpd.power_off_work); - queue_work(pm_wq, &sh7372_d4.genpd.power_off_work); - queue_work(pm_wq, &sh7372_a3rv.genpd.power_off_work); - queue_work(pm_wq, &sh7372_a3ri.genpd.power_off_work); - queue_work(pm_wq, &sh7372_a3sg.genpd.power_off_work); -} - void sh7372_init_pm_domain(struct sh7372_pm_domain *sh7372_pd) { struct generic_pm_domain *genpd = &sh7372_pd->genpd; @@ -157,8 +146,6 @@ void sh7372_init_pm_domain(struct sh7372_pm_domain *sh7372_pd) genpd->power_on = pd_power_up; } genpd->power_on(&sh7372_pd->genpd); - - shmobile_runtime_pm_late_init = sh7372_late_pm_domain_off; } void sh7372_add_device_to_domain(struct sh7372_pm_domain *sh7372_pd, diff --git a/arch/arm/mach-shmobile/pm_runtime.c b/arch/arm/mach-shmobile/pm_runtime.c index 2f6ded5..6ec454e 100644 --- a/arch/arm/mach-shmobile/pm_runtime.c +++ b/arch/arm/mach-shmobile/pm_runtime.c @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -57,12 +58,9 @@ static int __init sh_pm_runtime_init(void) } core_initcall(sh_pm_runtime_init); -void (*shmobile_runtime_pm_late_init)(void); - static int __init sh_pm_runtime_late_init(void) { - if (shmobile_runtime_pm_late_init) - shmobile_runtime_pm_late_init(); + pm_genpd_poweroff_unused(); return 0; } late_initcall(sh_pm_runtime_late_init); -- cgit v0.10.2 From 0bc5b2debb832191a42baea7ff59d2ca6ce9f7d5 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Thu, 14 Jul 2011 20:59:07 +0200 Subject: ARM / shmobile: Use genpd_queue_power_off_work() Make pd_power_down_a3rv() use genpd_queue_power_off_work() to queue up the powering off of the A4LC domain to avoid queuing it up when it is pending. Signed-off-by: Rafael J. Wysocki Acked-by: Magnus Damm diff --git a/arch/arm/mach-shmobile/pm-sh7372.c b/arch/arm/mach-shmobile/pm-sh7372.c index f47281a..0b07138 100644 --- a/arch/arm/mach-shmobile/pm-sh7372.c +++ b/arch/arm/mach-shmobile/pm-sh7372.c @@ -107,7 +107,7 @@ static int pd_power_down_a3rv(struct generic_pm_domain *genpd) /* try to power down A4LC after A3RV is requested off */ pm_genpd_poweron(&sh7372_a4lc.genpd); - queue_work(pm_wq, &sh7372_a4lc.genpd.power_off_work); + genpd_queue_power_off_work(&sh7372_a4lc.genpd); return ret; } diff --git a/drivers/base/power/domain.c b/drivers/base/power/domain.c index c2c537d..00ed4f3 100644 --- a/drivers/base/power/domain.c +++ b/drivers/base/power/domain.c @@ -222,7 +222,7 @@ static bool genpd_abort_poweroff(struct generic_pm_domain *genpd) * Queue up the execution of pm_genpd_poweroff() unless it's already been done * before. */ -static void genpd_queue_power_off_work(struct generic_pm_domain *genpd) +void genpd_queue_power_off_work(struct generic_pm_domain *genpd) { if (!work_pending(&genpd->power_off_work)) queue_work(pm_wq, &genpd->power_off_work); diff --git a/include/linux/pm_domain.h b/include/linux/pm_domain.h index 3e4f3d3..21097cb 100644 --- a/include/linux/pm_domain.h +++ b/include/linux/pm_domain.h @@ -73,6 +73,7 @@ extern void pm_genpd_init(struct generic_pm_domain *genpd, struct dev_power_governor *gov, bool is_off); extern int pm_genpd_poweron(struct generic_pm_domain *genpd); extern void pm_genpd_poweroff_unused(void); +extern void genpd_queue_power_off_work(struct generic_pm_domain *genpd); #else static inline int pm_genpd_add_device(struct generic_pm_domain *genpd, struct device *dev) @@ -101,6 +102,7 @@ static inline int pm_genpd_poweron(struct generic_pm_domain *genpd) return -ENOSYS; } static inline void pm_genpd_poweroff_unused(void) {} +static inline void genpd_queue_power_off_work(struct generic_pm_domain *gpd) {} #endif #endif /* _LINUX_PM_DOMAIN_H */ -- cgit v0.10.2 From d28054020f97c7c9f15327a53945f0f40ffc5d7a Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Thu, 14 Jul 2011 20:59:20 +0200 Subject: PM / Domains: Take .power_off() error code into account Currently pm_genpd_poweroff() discards error codes returned by the PM domain's .power_off() callback, because it's safer to always regard the domain as inaccessible to drivers after a failing .power_off(). Still, there are situations in which the low-level code may want to indicate that it doesn't want to power off the domain, so allow it to do that by returning -EBUSY from .power_off(). Signed-off-by: Rafael J. Wysocki Acked-by: Magnus Damm diff --git a/drivers/base/power/domain.c b/drivers/base/power/domain.c index 00ed4f3..be8714a 100644 --- a/drivers/base/power/domain.c +++ b/drivers/base/power/domain.c @@ -312,8 +312,16 @@ static int pm_genpd_poweroff(struct generic_pm_domain *genpd) } } - if (genpd->power_off) - genpd->power_off(genpd); + if (genpd->power_off) { + ret = genpd->power_off(genpd); + if (ret == -EBUSY) { + genpd_set_active(genpd); + if (parent) + genpd_release_lock(parent); + + goto out; + } + } genpd->status = GPD_STATE_POWER_OFF; -- cgit v0.10.2 From 5ca80817e231723f1399bff495854ba2171103ca Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Thu, 14 Jul 2011 20:59:35 +0200 Subject: ARM / shmobile: Return -EBUSY from A4LC power off if A3RV is active Since the A4LC should only be powered off if the A3RV is off, make the A4LC's power down routine return -EBUSY if A3RV is not off to indicate to the core that it doesn't want to power off the domain in that case. This will cause the core to regard A4LC as active, so the pm_genpd_poweron() in pd_power_down_a3rv() is not necessary any more. Signed-off-by: Rafael J. Wysocki Acked-by: Magnus Damm diff --git a/arch/arm/mach-shmobile/pm-sh7372.c b/arch/arm/mach-shmobile/pm-sh7372.c index 0b07138..933fb41 100644 --- a/arch/arm/mach-shmobile/pm-sh7372.c +++ b/arch/arm/mach-shmobile/pm-sh7372.c @@ -106,7 +106,6 @@ static int pd_power_down_a3rv(struct generic_pm_domain *genpd) int ret = pd_power_down(genpd); /* try to power down A4LC after A3RV is requested off */ - pm_genpd_poweron(&sh7372_a4lc.genpd); genpd_queue_power_off_work(&sh7372_a4lc.genpd); return ret; @@ -118,7 +117,7 @@ static int pd_power_down_a4lc(struct generic_pm_domain *genpd) if (!(__raw_readl(PSTR) & (1 << sh7372_a3rv.bit_shift))) return pd_power_down(genpd); - return 0; + return -EBUSY; } static bool pd_active_wakeup(struct device *dev) -- cgit v0.10.2