Hi,
The first of the following two patches adds common definitions and code
for supporting generic I/O power domains and the second patch shows how
it can be used by the example of ARM shmobile platform.
The patches are on top of the branch at:
git://git.kernel.org/pub/scm/linux/kernel/git/rafael/suspend-2.6.git power-domains
Comments welcome.
Thanks,
Rafael
From: Rafael J. Wysocki <[email protected]>
Introcude common headers, helper functions and callbacks allowing
platforms to use simple generic power domains for runtime power
management.
Introduce struct generic_power_domain to be used for representing
power domains that each contain a number of devices and may be
master 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_on() is
supposed to remove power from the entire power domain
and ->power_off() 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.
Signed-off-by: Rafael J. Wysocki <[email protected]>
---
drivers/base/power/Makefile | 2
drivers/base/power/domain.c | 416 ++++++++++++++++++++++++++++++++++++++++++++
include/linux/pm.h | 3
include/linux/pm_domain.h | 82 ++++++++
4 files changed, 501 insertions(+), 2 deletions(-)
Index: linux-2.6/include/linux/pm_domain.h
===================================================================
--- /dev/null
+++ linux-2.6/include/linux/pm_domain.h
@@ -0,0 +1,82 @@
+/*
+ * pm_domain.h - Definitions and headers related to device power domains.
+ *
+ * Copyright (C) 2011 Rafael J. Wysocki <[email protected]>, Renesas Electronics Corp.
+ *
+ * This file is released under the GPLv2.
+ */
+
+#ifndef _LINUX_PM_DOMAIN_H
+#define _LINUX_PM_DOMAIN_H
+
+#include <linux/device.h>
+
+struct dev_power_governor {
+ bool (*power_down_ok)(struct dev_power_domain *domain);
+};
+
+struct generic_power_domain {
+ struct dev_power_domain domain;
+ struct list_head node;
+ struct generic_power_domain *master;
+ struct list_head subdomain_list;
+ struct list_head device_list;
+ struct mutex lock;
+ struct dev_power_governor *gov;
+ bool power_is_off;
+ int (*power_off)(struct dev_power_domain *domain);
+ int (*power_on)(struct dev_power_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;
+};
+
+#ifdef CONFIG_PM_RUNTIME
+extern int pm_genpd_runtime_suspend(struct device *dev);
+extern int pm_genpd_runtime_resume(struct device *dev);
+#else
+#define pm_genpd_runtime_suspend NULL;
+#define pm_genpd_runtime_resume NULL;
+#endif
+
+#ifdef CONFIG_PM
+extern int pm_genpd_add_device(struct generic_power_domain *genpd,
+ struct device *dev);
+extern int pm_genpd_remove_device(struct generic_power_domain *genpd,
+ struct device *dev);
+extern int pm_genpd_add_subdomain(struct generic_power_domain *genpd,
+ struct generic_power_domain *new_subdomain);
+extern int pm_genpd_remove_subdomain(struct generic_power_domain *genpd,
+ struct generic_power_domain *target);
+extern void pm_genpd_init(struct generic_power_domain *genpd,
+ struct dev_power_governor *gov, bool is_off);
+#else
+static inline int pm_genpd_add_device(struct generic_power_domain *genpd,
+ struct device *dev)
+{
+ return -ENOSYS;
+}
+static inline int pm_genpd_remove_device(struct generic_power_domain *genpd,
+ struct device *dev)
+{
+ return -ENOSYS;
+}
+static inline int pm_genpd_add_subdomain(struct generic_power_domain *genpd,
+ struct generic_power_domain *new_sd)
+{
+ return -ENOSYS;
+}
+static inline int pm_genpd_remove_subdomain(struct generic_power_domain *genpd,
+ struct generic_power_domain *target)
+{
+ return -ENOSYS;
+}
+static inline void pm_genpd_init(struct generic_power_domain *genpd,
+ struct dev_power_governor *gov, bool is_off) {}
+#endif
+
+#endif /* _LINUX_PM_DOMAIN_H */
Index: linux-2.6/include/linux/pm.h
===================================================================
--- linux-2.6.orig/include/linux/pm.h
+++ linux-2.6/include/linux/pm.h
@@ -472,7 +472,8 @@ extern void update_pm_runtime_accounting
* subsystem-level and driver-level callbacks.
*/
struct dev_power_domain {
- struct dev_pm_ops ops;
+ struct dev_pm_ops ops;
+ void *platform_data;
};
/*
Index: linux-2.6/drivers/base/power/Makefile
===================================================================
--- linux-2.6.orig/drivers/base/power/Makefile
+++ linux-2.6/drivers/base/power/Makefile
@@ -1,4 +1,4 @@
-obj-$(CONFIG_PM) += sysfs.o generic_ops.o
+obj-$(CONFIG_PM) += sysfs.o generic_ops.o domain.o
obj-$(CONFIG_PM_SLEEP) += main.o wakeup.o
obj-$(CONFIG_PM_RUNTIME) += runtime.o
obj-$(CONFIG_PM_TRACE_RTC) += trace.o
Index: linux-2.6/drivers/base/power/domain.c
===================================================================
--- /dev/null
+++ linux-2.6/drivers/base/power/domain.c
@@ -0,0 +1,416 @@
+/*
+ * drivers/base/power/domain.c - Common code related to device power domains.
+ *
+ * Copyright (C) 2011 Rafael J. Wysocki <[email protected]>, Renesas Electronics Corp.
+ *
+ * This file is released under the GPLv2.
+ */
+
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/io.h>
+#include <linux/pm_runtime.h>
+#include <linux/pm_domain.h>
+#include <linux/slab.h>
+#include <linux/err.h>
+
+#ifdef CONFIG_PM_RUNTIME
+
+/**
+ * __pm_genpd_restore_device - Restore a pre-suspend state of a device.
+ * @dev: Device to restore the state of.
+ * @genpd: Power domain the device belongs to.
+ */
+static void __pm_genpd_restore_device(struct device *dev,
+ struct generic_power_domain *genpd)
+{
+ struct device_driver *drv = dev->driver;
+
+ if (genpd->start_device)
+ genpd->start_device(dev);
+
+ if (drv && drv->pm && drv->pm->runtime_resume)
+ drv->pm->runtime_resume(dev);
+
+ if (genpd->stop_device)
+ genpd->stop_device(dev);
+}
+
+/**
+ * __pm_genpd_poweroff - Remove power from a given power domain.
+ * @genpd: Power 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_power_domain *genpd)
+{
+ struct generic_power_domain *subdomain;
+ struct dev_list_entry *dle;
+ int ret;
+
+ if (genpd->power_is_off)
+ return 0;
+
+ list_for_each_entry(dle, &genpd->device_list, node)
+ if (!pm_runtime_suspended(dle->dev))
+ return -EBUSY;
+
+ list_for_each_entry_reverse(subdomain, &genpd->subdomain_list, node) {
+ mutex_lock(&subdomain->lock);
+ ret = __pm_genpd_poweroff(subdomain);
+ mutex_unlock(&subdomain->lock);
+ if (ret)
+ return ret;
+ }
+
+ 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->device_list, node) {
+ struct device *dev = dle->dev;
+ struct device_driver *drv = dev->driver;
+
+ if (genpd->start_device)
+ genpd->start_device(dev);
+
+ if (drv && drv->pm && drv->pm->runtime_suspend)
+ ret = drv->pm->runtime_suspend(dev);
+
+ if (genpd->stop_device)
+ genpd->stop_device(dev);
+
+ if (ret)
+ goto err_dev;
+ }
+
+ if (genpd->power_off)
+ genpd->power_off(&genpd->domain);
+
+ genpd->power_is_off = true;
+
+ return 0;
+
+ err_dev:
+ list_for_each_entry_continue(dle, &genpd->device_list, node)
+ __pm_genpd_restore_device(dle->dev, genpd);
+
+ return ret;
+}
+
+/**
+ * pm_genpd_poweroff - Remove power from a given power domain and its masters.
+ * @genpd: Power domain to power down.
+ *
+ * Try to remove power from @genpd and all of its masters in order to save as
+ * much power as possible.
+ */
+static int pm_genpd_poweroff(struct generic_power_domain *genpd)
+{
+ struct generic_power_domain *master;
+ int ret;
+
+ mutex_lock(&genpd->lock);
+ master = genpd->master;
+ if (master) {
+ mutex_unlock(&genpd->lock);
+ return pm_genpd_poweroff(master);
+ }
+ ret = __pm_genpd_poweroff(genpd);
+ mutex_unlock(&genpd->lock);
+
+ return ret;
+}
+
+/**
+ * pm_genpd_runtime_suspend - Suspend a device belonging to I/O power domain.
+ * @dev: Device to suspend.
+ *
+ * Carry out a runtime suspend of a device under the assumption that its
+ * pwr_domain field points to the domain member of an object of type
+ * struct generic_power_domain representing a power domain consisting of I/O
+ * devices.
+ */
+int pm_genpd_runtime_suspend(struct device *dev)
+{
+ struct generic_power_domain *genpd;
+
+ dev_dbg(dev, "%s()\n", __func__);
+
+ if (IS_ERR_OR_NULL(dev->pwr_domain))
+ return -EINVAL;
+
+ genpd = container_of(dev->pwr_domain,
+ struct generic_power_domain, domain);
+
+ if (genpd->stop_device) {
+ int ret = genpd->stop_device(dev);
+ if (ret)
+ return ret;
+ }
+
+ return pm_genpd_poweroff(genpd);
+}
+
+/**
+ * __pm_genpd_poweron - Restore power for a given power domain.
+ * @genpd: Power domain to power up.
+ *
+ * Restore power for @genpd and run runtime resume callbacks provided by all of
+ * its devices' drivers.
+ */
+static int __pm_genpd_poweron(struct generic_power_domain *genpd)
+{
+ struct dev_list_entry *dle;
+
+ if (!genpd->power_is_off)
+ return 0;
+
+ if (genpd->power_on) {
+ int ret = genpd->power_on(&genpd->domain);
+ if (ret)
+ return ret;
+ }
+
+ genpd->power_is_off = false;
+
+ list_for_each_entry(dle, &genpd->device_list, node)
+ __pm_genpd_restore_device(dle->dev, genpd);
+
+ return 0;
+}
+
+/**
+ * pm_genpd_poweron - Restore power for a given power domain and its masters.
+ * @genpd: Power domain to power up.
+ *
+ * Restore power for @genpd and all of its masters so that it is possible to
+ * resume a device belonging to it.
+ */
+static int pm_genpd_poweron(struct generic_power_domain *genpd)
+{
+ struct generic_power_domain *master;
+ int ret;
+
+ mutex_lock(&genpd->lock);
+ master = genpd->master;
+ if (master) {
+ mutex_unlock(&genpd->lock);
+
+ ret = pm_genpd_poweron(master);
+ if (ret)
+ return ret;
+
+ mutex_lock(&genpd->lock);
+ }
+ ret = __pm_genpd_poweron(genpd);
+ mutex_unlock(&genpd->lock);
+
+ return ret;
+}
+
+/**
+ * pm_genpd_runtime_resume - Resume a device belonging to I/O power domain.
+ * @dev: Device to suspend.
+ *
+ * Carry out a runtime resume of a device under the assumption that its
+ * pwr_domain field points to the domain member of an object of type
+ * struct generic_power_domain representing a power domain consisting of I/O
+ * devices.
+ */
+int pm_genpd_runtime_resume(struct device *dev)
+{
+ struct generic_power_domain *genpd;
+ int ret;
+
+ dev_dbg(dev, "%s()\n", __func__);
+
+ if (IS_ERR_OR_NULL(dev->pwr_domain))
+ return -EINVAL;
+
+ genpd = container_of(dev->pwr_domain,
+ struct generic_power_domain, domain);
+
+ ret = pm_genpd_poweron(genpd);
+ if (ret)
+ return ret;
+
+ if (genpd->start_device)
+ genpd->start_device(dev);
+
+ return 0;
+}
+
+#endif /* CONFIG_PM_RUNTIME */
+
+/**
+ * pm_genpd_add_device - Add a device to an I/O power domain.
+ * @genpd: Power domain to add the device to.
+ * @dev: Device to be added.
+ */
+int pm_genpd_add_device(struct generic_power_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);
+
+ list_for_each_entry(dle, &genpd->device_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;
+ list_add_tail(&dle->node, &genpd->device_list);
+
+ spin_lock_irq(&dev->power.lock);
+ dev->pwr_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 power domain.
+ * @genpd: Power domain to remove the device from.
+ * @dev: Device to be removed.
+ */
+int pm_genpd_remove_device(struct generic_power_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->device_list, node) {
+ if (dle->dev != dev)
+ continue;
+
+ spin_lock_irq(&dev->power.lock);
+ dev->pwr_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 power domain.
+ * @genpd: Master power domain to add the subdomain to.
+ * @new_subdomain: Subdomain to be added.
+ */
+int pm_genpd_add_subdomain(struct generic_power_domain *genpd,
+ struct generic_power_domain *new_subdomain)
+{
+ struct generic_power_domain *subdomain;
+ int ret = 0;
+
+ if (IS_ERR_OR_NULL(genpd) || IS_ERR_OR_NULL(new_subdomain))
+ return -EINVAL;
+
+ mutex_lock(&genpd->lock);
+
+ list_for_each_entry(subdomain, &genpd->subdomain_list, node)
+ if (subdomain == new_subdomain) {
+ ret = -EINVAL;
+ goto out;
+ }
+
+ mutex_lock(&new_subdomain->lock);
+ list_add_tail(&new_subdomain->node, &genpd->subdomain_list);
+ new_subdomain->master = genpd;
+ mutex_unlock(&new_subdomain->lock);
+
+ out:
+ mutex_unlock(&genpd->lock);
+
+ return ret;
+}
+
+/**
+ * pm_genpd_remove_subdomain - Remove a subdomain from an I/O power domain.
+ * @genpd: Master power domain to remove the subdomain from.
+ * @target: Subdomain to be removed.
+ */
+int pm_genpd_remove_subdomain(struct generic_power_domain *genpd,
+ struct generic_power_domain *target)
+{
+ struct generic_power_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->subdomain_list, node) {
+ if (subdomain != target)
+ continue;
+
+ mutex_lock(&subdomain->lock);
+ list_del(&subdomain->node);
+ subdomain->master = NULL;
+ mutex_unlock(&subdomain->lock);
+
+ ret = 0;
+ break;
+ }
+
+ mutex_unlock(&genpd->lock);
+
+ return ret;
+}
+
+/**
+ * pm_genpd_init - Initialize an I/O power domain object.
+ * @genpd: Power domain object to initialize.
+ */
+void pm_genpd_init(struct generic_power_domain *genpd,
+ struct dev_power_governor *gov, bool is_off)
+{
+ if (IS_ERR_OR_NULL(genpd))
+ return;
+
+ INIT_LIST_HEAD(&genpd->node);
+ genpd->master = NULL;
+ INIT_LIST_HEAD(&genpd->device_list);
+ INIT_LIST_HEAD(&genpd->subdomain_list);
+ mutex_init(&genpd->lock);
+ genpd->gov = gov;
+ 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;
+}
From: Rafael J. Wysocki <[email protected]>
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 <[email protected]>
---
arch/arm/mach-shmobile/Makefile | 4 +
arch/arm/mach-shmobile/board-mackerel.c | 3
arch/arm/mach-shmobile/include/mach/sh7372.h | 16 ++++
arch/arm/mach-shmobile/pm-sh7372.c | 98 +++++++++++++++++++++++++++
4 files changed, 121 insertions(+)
Index: linux-2.6/arch/arm/mach-shmobile/board-mackerel.c
===================================================================
--- linux-2.6.orig/arch/arm/mach-shmobile/board-mackerel.c
+++ linux-2.6/arch/arm/mach-shmobile/board-mackerel.c
@@ -1211,6 +1211,9 @@ static void __init mackerel_init(void)
i2c_register_board_info(1, i2c1_devices,
ARRAY_SIZE(i2c1_devices));
+ sh7372_add_device_to_domain(SH7372_A4LC, &lcdc_device);
+ sh7372_add_device_to_domain(SH7372_A4LC, &hdmi_lcdc_device);
+
sh7372_add_standard_devices();
platform_add_devices(mackerel_devices, ARRAY_SIZE(mackerel_devices));
Index: linux-2.6/arch/arm/mach-shmobile/pm-sh7372.c
===================================================================
--- /dev/null
+++ linux-2.6/arch/arm/mach-shmobile/pm-sh7372.c
@@ -0,0 +1,98 @@
+/*
+ * arch/arm/mach-shmobile/pm-sh7372.c
+ *
+ * Power domains support code for SH7372
+ *
+ * Copyright (C) 2011 Rafael J. Wysocki <[email protected]>, Renesas Electronics Corp.
+ *
+ * This file is subject to the terms and conditions of the GNU General Public
+ * License v2. See the file "COPYING" in the main directory of this archive
+ * for more details.
+ */
+
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/io.h>
+#include <linux/pm_runtime.h>
+#include <linux/platform_device.h>
+
+#include <mach/sh7372.h>
+
+struct sh7372_domain_data {
+ unsigned int bit_shift;
+};
+
+#define SPDCR 0xe6180008
+#define SWUCR 0xe6180014
+#define PSTR 0xe6180080
+
+static int pd_power_down(struct dev_power_domain *domain)
+{
+ struct sh7372_domain_data *dd = domain->platform_data;
+ unsigned int mask = 1 << dd->bit_shift;
+
+ if (__raw_readl(PSTR) & mask) {
+ __raw_writel(mask, SPDCR);
+
+ while (__raw_readl(SPDCR) & mask) {}
+
+ pr_info("sh7372 power domain down 0x%08x -> PSTR = 0x%08x\n",
+ mask, __raw_readl(PSTR));
+ }
+
+ return 0;
+}
+
+static int pd_power_up(struct dev_power_domain *domain)
+{
+ struct sh7372_domain_data *dd = domain->platform_data;
+ unsigned int mask = 1 << dd->bit_shift;
+
+ if (!(__raw_readl(PSTR) & mask)) {
+ __raw_writel(mask, SWUCR);
+
+ while (__raw_readl(SWUCR) & mask) {}
+
+ pr_info("sh7372 power domain up 0x%08x -> PSTR = 0x%08x\n",
+ mask, __raw_readl(PSTR));
+ }
+
+ return 0;
+}
+
+static struct sh7372_domain_data sh7372_a4lc_domain_data = {
+ .bit_shift = 1,
+};
+
+struct generic_power_domain sh7372_a4lc_domain = {
+ .domain.ops = { USE_PLATFORM_PM_SLEEP_OPS },
+ .domain.platform_data = &sh7372_a4lc_domain_data,
+};
+
+static void sh7372_init_domain(struct generic_power_domain *domain)
+{
+ pm_genpd_init(domain, NULL, false);
+ domain->stop_device = pm_runtime_clk_suspend;
+ domain->start_device = pm_runtime_clk_resume;
+ domain->power_off = pd_power_down;
+ domain->power_on = pd_power_up;
+}
+
+void sh7372_add_device_to_domain(struct generic_power_domain *domain,
+ struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+
+ if (!dev->power.subsys_data) {
+ pm_runtime_clk_init(dev);
+ pm_runtime_clk_add(dev, NULL);
+ }
+ pm_genpd_add_device(domain, dev);
+}
+
+static int __init sh7372_power_domains_init(void)
+{
+ sh7372_init_domain(&sh7372_a4lc_domain);
+ return 0;
+}
+core_initcall(sh7372_power_domains_init);
Index: linux-2.6/arch/arm/mach-shmobile/include/mach/sh7372.h
===================================================================
--- linux-2.6.orig/arch/arm/mach-shmobile/include/mach/sh7372.h
+++ linux-2.6/arch/arm/mach-shmobile/include/mach/sh7372.h
@@ -12,6 +12,7 @@
#define __ASM_SH7372_H__
#include <linux/sh_clk.h>
+#include <linux/pm_domain.h>
/*
* Pin Function Controller:
@@ -469,4 +470,19 @@ extern struct clk sh7372_fsibck_clk;
extern struct clk sh7372_fsidiva_clk;
extern struct clk sh7372_fsidivb_clk;
+struct platform_device;
+
+#ifdef CONFIG_PM
+extern struct generic_power_domain sh7372_a4lc_domain;
+#define SH7372_A4LC (&sh7372_a4lc_domain)
+
+extern void sh7372_add_device_to_domain(struct generic_power_domain *domain,
+ struct platform_device *pdev);
+#else
+#define SH7372_A4LC NULL
+
+static inline void sh7372_add_device_to_domain(struct generic_power_domain *dom,
+ struct platform_device *pd) {}
+#endif /* CONFIG_PM */
+
#endif /* __ASM_SH7372_H__ */
Index: linux-2.6/arch/arm/mach-shmobile/Makefile
===================================================================
--- linux-2.6.orig/arch/arm/mach-shmobile/Makefile
+++ linux-2.6/arch/arm/mach-shmobile/Makefile
@@ -37,6 +37,10 @@ obj-$(CONFIG_MACH_AP4EVB) += board-ap4ev
obj-$(CONFIG_MACH_AG5EVM) += board-ag5evm.o
obj-$(CONFIG_MACH_MACKEREL) += board-mackerel.o
+# PM objects
+pm-$(CONFIG_ARCH_SH7372) += pm-sh7372.o
+
# Framework support
obj-$(CONFIG_SMP) += $(smp-y)
obj-$(CONFIG_GENERIC_GPIO) += $(pfc-y)
+obj-$(CONFIG_PM) += $(pm-y)
On Fri, Apr 29, 2011 at 8:54 AM, Rafael J. Wysocki <[email protected]> wrote:
> From: Rafael J. Wysocki <[email protected]>
>
> Introcude common headers, helper functions and callbacks allowing
> platforms to use simple generic power domains for runtime power
> management.
>
> Introduce struct generic_power_domain to be used for representing
> power domains that each contain a number of devices and may be
> master 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_on() is
> supposed to remove power from the entire power domain
> and ->power_off() 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.
>
> Signed-off-by: Rafael J. Wysocki <[email protected]>
Hi!
The pm_domain is assuming that there is at most one powerdomain per
device, which is perfectly fine with my machines and the concept of
in-SoC powerdomains.
However, if so, wouldn't it be possible to simply set powerdomain by
setting a device's parent?
For example, for a device, struct device *dev, and a powerdomain,
struct device *pd, use
"dev.parent = pd;", and let runtime-pm handle including the
"dev_power_governor" in the pm_domain's pm related code.
Cheers!
- MyungJoo
--
MyungJoo Ham, Ph.D.
Mobile Software Platform Lab,
Digital Media and Communications (DMC) Business
Samsung Electronics
cell: 82-10-6714-2858
On Friday, April 29, 2011, MyungJoo Ham wrote:
> On Fri, Apr 29, 2011 at 8:54 AM, Rafael J. Wysocki <[email protected]> wrote:
> > From: Rafael J. Wysocki <[email protected]>
> >
> > Introcude common headers, helper functions and callbacks allowing
> > platforms to use simple generic power domains for runtime power
> > management.
> >
> > Introduce struct generic_power_domain to be used for representing
> > power domains that each contain a number of devices and may be
> > master 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_on() is
> > supposed to remove power from the entire power domain
> > and ->power_off() 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.
> >
> > Signed-off-by: Rafael J. Wysocki <[email protected]>
>
> Hi!
>
> The pm_domain is assuming that there is at most one powerdomain per
> device, which is perfectly fine with my machines and the concept of
> in-SoC powerdomains.
>
> However, if so, wouldn't it be possible to simply set powerdomain by
> setting a device's parent?
> For example, for a device, struct device *dev, and a powerdomain,
> struct device *pd, use
> "dev.parent = pd;", and let runtime-pm handle including the
> "dev_power_governor" in the pm_domain's pm related code.
Well, not really. There are a few things to consider.
First, in general, there may be devices that have a real parent and belong
to a power domain at the same time, so we can't "steal" the parent
pointers from them.
Second, we generally don't want subsystem-level runtime PM callbacks to be
executed directly for devices in a power domain and the domain's
callbacks are used instead. Normally, subsystem (e.g. bus type) callbacks
execute drivers' callbacks, but for devices in a power domain we may not
really want the drivers' callbacks to be executed until the domain is ready
for power removal (i.e. pm_runtime_suspend() or equivalent has been called
for all devices in the domain already). If we were to use the approach
you suggest, we'd need to override the subsystem's runtime PM callbacks for
the devices in the power domain _anyway_ and _additionally_ we'd need to
represent that power domain as the parent of all of those devices. While that
might work in principle, I think it's more straightforward to do it like
in the $subject patch. [In this approach a power domain may be regarded as an
entity providing PM callbacks replacing the subsystem ones for devices
belonging to it and those callbacks should be sufficient to make things work
without additional modifications of the device hierarchy.]
Next, if power domains were to be represented by device objects, we'd
need to create subsystem (e.g. bus type) objects providing PM callbacks
for them (so, for example, we'd have a "generic power domain" bus type)
and we'd probably need drivers for power domain "devices". It seems that
this additional infrastructure would be a bit overweight.
Finally, if a power domain were represented as a parent of its devices,
its (or its subsystem's) runtime PM callbacks would end up executing the
runtime PM callbacks provided by its child devices' drivers, which would be
kind of unusual, so to speak.
There probably are more arguments, but I guess the above are sufficient. :-)
Thanks,
Rafael
On Fri, Apr 29, 2011 at 01:54:55AM +0200, Rafael J. Wysocki wrote:
> From: Rafael J. Wysocki <[email protected]>
>
> Introcude common headers, helper functions and callbacks allowing
> platforms to use simple generic power domains for runtime power
> management.
>
> Introduce struct generic_power_domain to be used for representing
> power domains that each contain a number of devices and may be
> master 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_on() is
> supposed to remove power from the entire power domain
> and ->power_off() 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.
>
> Signed-off-by: Rafael J. Wysocki <[email protected]>
> ---
> drivers/base/power/Makefile | 2
> drivers/base/power/domain.c | 416 ++++++++++++++++++++++++++++++++++++++++++++
> include/linux/pm.h | 3
> include/linux/pm_domain.h | 82 ++++++++
> 4 files changed, 501 insertions(+), 2 deletions(-)
If this solves the power domain issue, and no one has any problems with
getting to work on their platforms, it looks great to me.
Very nice job:
Acked-by: Greg Kroah-Hartman <[email protected]>
thanks,
greg k-h
From: Rafael J. Wysocki <[email protected]>
Introcude common headers, helper functions and callbacks allowing
platforms to use simple generic power domains for runtime power
management.
Introduce struct generic_power_domain to be used for representing
power domains that each contain a number of devices and may be
master 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_on() is
supposed to remove power from the entire power domain
and ->power_off() 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.
Signed-off-by: Rafael J. Wysocki <[email protected]>
Acked-by: Greg Kroah-Hartman <[email protected]>
---
Hi,
This version of the patch fixes a bug that caused pm_runtime_suspend()
(and equivalent) to return -EBUSY erroneously when suspending the last
active device in a power domain. It has been tested on an ARM shmobile
system.
Greg, I hope your ACK is still valid. :-)
Thanks,
Rafael
---
drivers/base/power/Makefile | 2
drivers/base/power/domain.c | 439 ++++++++++++++++++++++++++++++++++++++++++++
include/linux/pm.h | 3
include/linux/pm_domain.h | 83 ++++++++
4 files changed, 525 insertions(+), 2 deletions(-)
Index: linux-2.6/include/linux/pm_domain.h
===================================================================
--- /dev/null
+++ linux-2.6/include/linux/pm_domain.h
@@ -0,0 +1,83 @@
+/*
+ * pm_domain.h - Definitions and headers related to device power domains.
+ *
+ * Copyright (C) 2011 Rafael J. Wysocki <[email protected]>, Renesas Electronics Corp.
+ *
+ * This file is released under the GPLv2.
+ */
+
+#ifndef _LINUX_PM_DOMAIN_H
+#define _LINUX_PM_DOMAIN_H
+
+#include <linux/device.h>
+
+struct dev_power_governor {
+ bool (*power_down_ok)(struct dev_power_domain *domain);
+};
+
+struct generic_power_domain {
+ struct dev_power_domain domain;
+ struct list_head node;
+ struct generic_power_domain *master;
+ struct list_head subdomain_list;
+ struct list_head device_list;
+ struct mutex lock;
+ struct dev_power_governor *gov;
+ unsigned int in_progress;
+ bool power_is_off;
+ int (*power_off)(struct dev_power_domain *domain);
+ int (*power_on)(struct dev_power_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;
+};
+
+#ifdef CONFIG_PM_RUNTIME
+extern int pm_genpd_runtime_suspend(struct device *dev);
+extern int pm_genpd_runtime_resume(struct device *dev);
+#else
+#define pm_genpd_runtime_suspend NULL;
+#define pm_genpd_runtime_resume NULL;
+#endif
+
+#ifdef CONFIG_PM
+extern int pm_genpd_add_device(struct generic_power_domain *genpd,
+ struct device *dev);
+extern int pm_genpd_remove_device(struct generic_power_domain *genpd,
+ struct device *dev);
+extern int pm_genpd_add_subdomain(struct generic_power_domain *genpd,
+ struct generic_power_domain *new_subdomain);
+extern int pm_genpd_remove_subdomain(struct generic_power_domain *genpd,
+ struct generic_power_domain *target);
+extern void pm_genpd_init(struct generic_power_domain *genpd,
+ struct dev_power_governor *gov, bool is_off);
+#else
+static inline int pm_genpd_add_device(struct generic_power_domain *genpd,
+ struct device *dev)
+{
+ return -ENOSYS;
+}
+static inline int pm_genpd_remove_device(struct generic_power_domain *genpd,
+ struct device *dev)
+{
+ return -ENOSYS;
+}
+static inline int pm_genpd_add_subdomain(struct generic_power_domain *genpd,
+ struct generic_power_domain *new_sd)
+{
+ return -ENOSYS;
+}
+static inline int pm_genpd_remove_subdomain(struct generic_power_domain *genpd,
+ struct generic_power_domain *target)
+{
+ return -ENOSYS;
+}
+static inline void pm_genpd_init(struct generic_power_domain *genpd,
+ struct dev_power_governor *gov, bool is_off) {}
+#endif
+
+#endif /* _LINUX_PM_DOMAIN_H */
Index: linux-2.6/include/linux/pm.h
===================================================================
--- linux-2.6.orig/include/linux/pm.h
+++ linux-2.6/include/linux/pm.h
@@ -472,7 +472,8 @@ extern void update_pm_runtime_accounting
* subsystem-level and driver-level callbacks.
*/
struct dev_power_domain {
- struct dev_pm_ops ops;
+ struct dev_pm_ops ops;
+ void *platform_data;
};
/*
Index: linux-2.6/drivers/base/power/Makefile
===================================================================
--- linux-2.6.orig/drivers/base/power/Makefile
+++ linux-2.6/drivers/base/power/Makefile
@@ -1,4 +1,4 @@
-obj-$(CONFIG_PM) += sysfs.o generic_ops.o
+obj-$(CONFIG_PM) += sysfs.o generic_ops.o domain.o
obj-$(CONFIG_PM_SLEEP) += main.o wakeup.o
obj-$(CONFIG_PM_RUNTIME) += runtime.o
obj-$(CONFIG_PM_TRACE_RTC) += trace.o
Index: linux-2.6/drivers/base/power/domain.c
===================================================================
--- /dev/null
+++ linux-2.6/drivers/base/power/domain.c
@@ -0,0 +1,439 @@
+/*
+ * drivers/base/power/domain.c - Common code related to device power domains.
+ *
+ * Copyright (C) 2011 Rafael J. Wysocki <[email protected]>, Renesas Electronics Corp.
+ *
+ * This file is released under the GPLv2.
+ */
+
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/io.h>
+#include <linux/pm_runtime.h>
+#include <linux/pm_domain.h>
+#include <linux/slab.h>
+#include <linux/err.h>
+
+#ifdef CONFIG_PM_RUNTIME
+
+/**
+ * __pm_genpd_restore_device - Restore a pre-suspend state of a device.
+ * @dev: Device to restore the state of.
+ * @genpd: Power domain the device belongs to.
+ */
+static void __pm_genpd_restore_device(struct device *dev,
+ struct generic_power_domain *genpd)
+{
+ struct device_driver *drv = dev->driver;
+
+ if (genpd->start_device)
+ genpd->start_device(dev);
+
+ if (drv && drv->pm && drv->pm->runtime_resume)
+ drv->pm->runtime_resume(dev);
+
+ if (genpd->stop_device)
+ genpd->stop_device(dev);
+}
+
+/**
+ * __pm_genpd_poweroff - Remove power from a given power domain.
+ * @genpd: Power 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_power_domain *genpd)
+{
+ struct generic_power_domain *subdomain;
+ struct dev_list_entry *dle;
+ unsigned int not_suspended;
+ int ret;
+
+ if (genpd->power_is_off)
+ return 0;
+
+ not_suspended = 0;
+ list_for_each_entry(dle, &genpd->device_list, node)
+ if (!pm_runtime_suspended(dle->dev))
+ not_suspended++;
+
+ if (not_suspended > genpd->in_progress)
+ return -EBUSY;
+
+ list_for_each_entry_reverse(subdomain, &genpd->subdomain_list, node) {
+ mutex_lock(&subdomain->lock);
+ ret = __pm_genpd_poweroff(subdomain);
+ mutex_unlock(&subdomain->lock);
+ if (ret)
+ return ret;
+ }
+
+ 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->device_list, node) {
+ struct device *dev = dle->dev;
+ struct device_driver *drv = dev->driver;
+
+ if (genpd->start_device)
+ genpd->start_device(dev);
+
+ if (drv && drv->pm && drv->pm->runtime_suspend)
+ ret = drv->pm->runtime_suspend(dev);
+
+ if (genpd->stop_device)
+ genpd->stop_device(dev);
+
+ if (ret)
+ goto err_dev;
+ }
+
+ if (genpd->power_off)
+ genpd->power_off(&genpd->domain);
+
+ genpd->power_is_off = true;
+
+ return 0;
+
+ err_dev:
+ list_for_each_entry_continue(dle, &genpd->device_list, node)
+ __pm_genpd_restore_device(dle->dev, genpd);
+
+ return ret;
+}
+
+/**
+ * pm_genpd_poweroff - Remove power from a given power domain and its masters.
+ * @genpd: Power domain to power down.
+ *
+ * Try to remove power from @genpd and all of its masters in order to save as
+ * much power as possible.
+ */
+static void pm_genpd_poweroff(struct generic_power_domain *genpd)
+{
+ struct generic_power_domain *master;
+
+ mutex_lock(&genpd->lock);
+ master = genpd->master;
+ if (master) {
+ mutex_unlock(&genpd->lock);
+ pm_genpd_poweroff(master);
+ return;
+ }
+ __pm_genpd_poweroff(genpd);
+ mutex_unlock(&genpd->lock);
+}
+
+/**
+ * pm_genpd_runtime_suspend - Suspend a device belonging to I/O power domain.
+ * @dev: Device to suspend.
+ *
+ * Carry out a runtime suspend of a device under the assumption that its
+ * pwr_domain field points to the domain member of an object of type
+ * struct generic_power_domain representing a power domain consisting of I/O
+ * devices.
+ */
+int pm_genpd_runtime_suspend(struct device *dev)
+{
+ struct generic_power_domain *genpd, *master;
+
+ dev_dbg(dev, "%s()\n", __func__);
+
+ if (IS_ERR_OR_NULL(dev->pwr_domain))
+ return -EINVAL;
+
+ genpd = container_of(dev->pwr_domain,
+ struct generic_power_domain, domain);
+
+ mutex_lock(&genpd->lock);
+
+ if (genpd->stop_device) {
+ int ret = genpd->stop_device(dev);
+ if (ret)
+ goto out;
+ }
+ genpd->in_progress++;
+
+ master = genpd->master;
+ if (master) {
+ mutex_unlock(&genpd->lock);
+
+ pm_genpd_poweroff(master);
+
+ mutex_lock(&genpd->lock);
+ } else {
+ __pm_genpd_poweroff(genpd);
+ }
+
+ genpd->in_progress--;
+
+ out:
+ mutex_unlock(&genpd->lock);
+
+ return 0;
+}
+
+/**
+ * __pm_genpd_poweron - Restore power for a given power domain.
+ * @genpd: Power domain to power up.
+ *
+ * Restore power for @genpd and run runtime resume callbacks provided by all of
+ * its devices' drivers.
+ */
+static int __pm_genpd_poweron(struct generic_power_domain *genpd)
+{
+ struct dev_list_entry *dle;
+
+ if (!genpd->power_is_off)
+ return 0;
+
+ if (genpd->power_on) {
+ int ret = genpd->power_on(&genpd->domain);
+ if (ret)
+ return ret;
+ }
+
+ genpd->power_is_off = false;
+
+ list_for_each_entry(dle, &genpd->device_list, node)
+ __pm_genpd_restore_device(dle->dev, genpd);
+
+ return 0;
+}
+
+/**
+ * pm_genpd_poweron - Restore power for a given power domain and its masters.
+ * @genpd: Power domain to power up.
+ *
+ * Restore power for @genpd and all of its masters so that it is possible to
+ * resume a device belonging to it.
+ */
+static int pm_genpd_poweron(struct generic_power_domain *genpd)
+{
+ struct generic_power_domain *master;
+ int ret;
+
+ mutex_lock(&genpd->lock);
+ master = genpd->master;
+ if (master) {
+ mutex_unlock(&genpd->lock);
+
+ ret = pm_genpd_poweron(master);
+ if (ret)
+ return ret;
+
+ mutex_lock(&genpd->lock);
+ }
+ ret = __pm_genpd_poweron(genpd);
+ mutex_unlock(&genpd->lock);
+
+ return ret;
+}
+
+/**
+ * pm_genpd_runtime_resume - Resume a device belonging to I/O power domain.
+ * @dev: Device to suspend.
+ *
+ * Carry out a runtime resume of a device under the assumption that its
+ * pwr_domain field points to the domain member of an object of type
+ * struct generic_power_domain representing a power domain consisting of I/O
+ * devices.
+ */
+int pm_genpd_runtime_resume(struct device *dev)
+{
+ struct generic_power_domain *genpd;
+ int ret;
+
+ dev_dbg(dev, "%s()\n", __func__);
+
+ if (IS_ERR_OR_NULL(dev->pwr_domain))
+ return -EINVAL;
+
+ genpd = container_of(dev->pwr_domain,
+ struct generic_power_domain, domain);
+
+ ret = pm_genpd_poweron(genpd);
+ if (ret)
+ return ret;
+
+ if (genpd->start_device)
+ genpd->start_device(dev);
+
+ return 0;
+}
+
+#endif /* CONFIG_PM_RUNTIME */
+
+/**
+ * pm_genpd_add_device - Add a device to an I/O power domain.
+ * @genpd: Power domain to add the device to.
+ * @dev: Device to be added.
+ */
+int pm_genpd_add_device(struct generic_power_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);
+
+ list_for_each_entry(dle, &genpd->device_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;
+ list_add_tail(&dle->node, &genpd->device_list);
+
+ spin_lock_irq(&dev->power.lock);
+ dev->pwr_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 power domain.
+ * @genpd: Power domain to remove the device from.
+ * @dev: Device to be removed.
+ */
+int pm_genpd_remove_device(struct generic_power_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->device_list, node) {
+ if (dle->dev != dev)
+ continue;
+
+ spin_lock_irq(&dev->power.lock);
+ dev->pwr_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 power domain.
+ * @genpd: Master power domain to add the subdomain to.
+ * @new_subdomain: Subdomain to be added.
+ */
+int pm_genpd_add_subdomain(struct generic_power_domain *genpd,
+ struct generic_power_domain *new_subdomain)
+{
+ struct generic_power_domain *subdomain;
+ int ret = 0;
+
+ if (IS_ERR_OR_NULL(genpd) || IS_ERR_OR_NULL(new_subdomain))
+ return -EINVAL;
+
+ mutex_lock(&genpd->lock);
+
+ list_for_each_entry(subdomain, &genpd->subdomain_list, node)
+ if (subdomain == new_subdomain) {
+ ret = -EINVAL;
+ goto out;
+ }
+
+ mutex_lock(&new_subdomain->lock);
+ list_add_tail(&new_subdomain->node, &genpd->subdomain_list);
+ new_subdomain->master = genpd;
+ mutex_unlock(&new_subdomain->lock);
+
+ out:
+ mutex_unlock(&genpd->lock);
+
+ return ret;
+}
+
+/**
+ * pm_genpd_remove_subdomain - Remove a subdomain from an I/O power domain.
+ * @genpd: Master power domain to remove the subdomain from.
+ * @target: Subdomain to be removed.
+ */
+int pm_genpd_remove_subdomain(struct generic_power_domain *genpd,
+ struct generic_power_domain *target)
+{
+ struct generic_power_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->subdomain_list, node) {
+ if (subdomain != target)
+ continue;
+
+ mutex_lock(&subdomain->lock);
+ list_del(&subdomain->node);
+ subdomain->master = NULL;
+ mutex_unlock(&subdomain->lock);
+
+ ret = 0;
+ break;
+ }
+
+ mutex_unlock(&genpd->lock);
+
+ return ret;
+}
+
+/**
+ * pm_genpd_init - Initialize an I/O power domain object.
+ * @genpd: Power domain object to initialize.
+ */
+void pm_genpd_init(struct generic_power_domain *genpd,
+ struct dev_power_governor *gov, bool is_off)
+{
+ if (IS_ERR_OR_NULL(genpd))
+ return;
+
+ INIT_LIST_HEAD(&genpd->node);
+ genpd->master = NULL;
+ INIT_LIST_HEAD(&genpd->device_list);
+ INIT_LIST_HEAD(&genpd->subdomain_list);
+ mutex_init(&genpd->lock);
+ genpd->gov = gov;
+ genpd->in_progress = 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;
+}
From: Rafael J. Wysocki <[email protected]>
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 <[email protected]>
---
Hi,
This version of the patch has been modified to make it more
straightforward to add more power domains to SH7372 in the future.
Thanks,
Rafael
---
arch/arm/mach-shmobile/Makefile | 4 +
arch/arm/mach-shmobile/board-mackerel.c | 3
arch/arm/mach-shmobile/include/mach/sh7372.h | 16 ++++
arch/arm/mach-shmobile/pm-sh7372.c | 98 +++++++++++++++++++++++++++
include/linux/platform_device.h | 22 +++++-
5 files changed, 142 insertions(+), 1 deletion(-)
Index: linux-2.6/arch/arm/mach-shmobile/board-mackerel.c
===================================================================
--- linux-2.6.orig/arch/arm/mach-shmobile/board-mackerel.c
+++ linux-2.6/arch/arm/mach-shmobile/board-mackerel.c
@@ -1215,6 +1215,9 @@ static void __init mackerel_init(void)
platform_add_devices(mackerel_devices, ARRAY_SIZE(mackerel_devices));
+ sh7372_add_device_to_domain(SH7372_A4LC, &lcdc_device);
+ sh7372_add_device_to_domain(SH7372_A4LC, &hdmi_lcdc_device);
+
hdmi_init_pm_clock();
}
Index: linux-2.6/arch/arm/mach-shmobile/pm-sh7372.c
===================================================================
--- /dev/null
+++ linux-2.6/arch/arm/mach-shmobile/pm-sh7372.c
@@ -0,0 +1,98 @@
+/*
+ * arch/arm/mach-shmobile/pm-sh7372.c
+ *
+ * Power domains support code for SH7372
+ *
+ * Copyright (C) 2011 Rafael J. Wysocki <[email protected]>, Renesas Electronics Corp.
+ *
+ * This file is subject to the terms and conditions of the GNU General Public
+ * License v2. See the file "COPYING" in the main directory of this archive
+ * for more details.
+ */
+
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/io.h>
+#include <linux/pm_runtime.h>
+#include <linux/platform_device.h>
+
+#include <mach/sh7372.h>
+
+struct sh7372_domain_data {
+ unsigned int bit_shift;
+};
+
+#define SPDCR 0xe6180008
+#define SWUCR 0xe6180014
+#define PSTR 0xe6180080
+
+static int pd_power_down(struct dev_power_domain *domain)
+{
+ struct sh7372_domain_data *dd = domain->platform_data;
+ unsigned int mask = 1 << dd->bit_shift;
+
+ if (__raw_readl(PSTR) & mask) {
+ __raw_writel(mask, SPDCR);
+
+ while (__raw_readl(SPDCR) & mask) {}
+
+ pr_debug("sh7372 power domain down 0x%08x -> PSTR = 0x%08x\n",
+ mask, __raw_readl(PSTR));
+ }
+
+ return 0;
+}
+
+static int pd_power_up(struct dev_power_domain *domain)
+{
+ struct sh7372_domain_data *dd = domain->platform_data;
+ unsigned int mask = 1 << dd->bit_shift;
+
+ if (!(__raw_readl(PSTR) & mask)) {
+ __raw_writel(mask, SWUCR);
+
+ while (__raw_readl(SWUCR) & mask) {}
+
+ pr_debug("sh7372 power domain up 0x%08x -> PSTR = 0x%08x\n",
+ mask, __raw_readl(PSTR));
+ }
+
+ return 0;
+}
+
+static void sh7372_init_domain(struct generic_power_domain *domain,
+ struct sh7372_domain_data *pdata)
+{
+ pm_genpd_init(domain, NULL, false);
+ add_platform_pm_sleep_ops(&domain->domain.ops);
+ domain->domain.platform_data = pdata;
+ domain->stop_device = pm_runtime_clk_suspend;
+ domain->start_device = pm_runtime_clk_resume;
+ domain->power_off = pd_power_down;
+ domain->power_on = pd_power_up;
+}
+
+void sh7372_add_device_to_domain(struct generic_power_domain *domain,
+ struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+
+ if (!dev->power.subsys_data) {
+ pm_runtime_clk_init(dev);
+ pm_runtime_clk_add(dev, NULL);
+ }
+ pm_genpd_add_device(domain, dev);
+}
+
+static struct sh7372_domain_data sh7372_a4lc_domain_data = {
+ .bit_shift = 1,
+};
+
+struct generic_power_domain sh7372_a4lc_domain;
+
+static int __init sh7372_power_domains_init(void)
+{
+ sh7372_init_domain(&sh7372_a4lc_domain, &sh7372_a4lc_domain_data);
+ return 0;
+}
+core_initcall(sh7372_power_domains_init);
Index: linux-2.6/arch/arm/mach-shmobile/include/mach/sh7372.h
===================================================================
--- linux-2.6.orig/arch/arm/mach-shmobile/include/mach/sh7372.h
+++ linux-2.6/arch/arm/mach-shmobile/include/mach/sh7372.h
@@ -12,6 +12,7 @@
#define __ASM_SH7372_H__
#include <linux/sh_clk.h>
+#include <linux/pm_domain.h>
/*
* Pin Function Controller:
@@ -469,4 +470,19 @@ extern struct clk sh7372_fsibck_clk;
extern struct clk sh7372_fsidiva_clk;
extern struct clk sh7372_fsidivb_clk;
+struct platform_device;
+
+#ifdef CONFIG_PM
+extern struct generic_power_domain sh7372_a4lc_domain;
+#define SH7372_A4LC (&sh7372_a4lc_domain)
+
+extern void sh7372_add_device_to_domain(struct generic_power_domain *domain,
+ struct platform_device *pdev);
+#else
+#define SH7372_A4LC NULL
+
+static inline void sh7372_add_device_to_domain(struct generic_power_domain *dom,
+ struct platform_device *pd) {}
+#endif /* CONFIG_PM */
+
#endif /* __ASM_SH7372_H__ */
Index: linux-2.6/arch/arm/mach-shmobile/Makefile
===================================================================
--- linux-2.6.orig/arch/arm/mach-shmobile/Makefile
+++ linux-2.6/arch/arm/mach-shmobile/Makefile
@@ -37,6 +37,10 @@ obj-$(CONFIG_MACH_AP4EVB) += board-ap4ev
obj-$(CONFIG_MACH_AG5EVM) += board-ag5evm.o
obj-$(CONFIG_MACH_MACKEREL) += board-mackerel.o
+# PM objects
+pm-$(CONFIG_ARCH_SH7372) += pm-sh7372.o
+
# Framework support
obj-$(CONFIG_SMP) += $(smp-y)
obj-$(CONFIG_GENERIC_GPIO) += $(pfc-y)
+obj-$(CONFIG_PM) += $(pm-y)
Index: linux-2.6/include/linux/platform_device.h
===================================================================
--- linux-2.6.orig/include/linux/platform_device.h
+++ linux-2.6/include/linux/platform_device.h
@@ -258,8 +258,28 @@ extern int platform_pm_restore_noirq(str
.thaw_noirq = platform_pm_thaw_noirq, \
.poweroff_noirq = platform_pm_poweroff_noirq, \
.restore_noirq = platform_pm_restore_noirq,
+
+static inline void add_platform_pm_sleep_ops(struct dev_pm_ops *pm_ops)
+{
+ pm_ops->prepare = platform_pm_prepare;
+ pm_ops->complete = platform_pm_complete;
+ pm_ops->suspend = platform_pm_suspend;
+ pm_ops->resume = platform_pm_resume;
+ pm_ops->freeze = platform_pm_freeze;
+ pm_ops->thaw = platform_pm_thaw;
+ pm_ops->poweroff = platform_pm_poweroff;
+ pm_ops->restore = platform_pm_restore;
+ pm_ops->suspend_noirq = platform_pm_suspend_noirq;
+ pm_ops->resume_noirq = platform_pm_resume_noirq;
+ pm_ops->freeze_noirq = platform_pm_freeze_noirq;
+ pm_ops->thaw_noirq = platform_pm_thaw_noirq;
+ pm_ops->poweroff_noirq = platform_pm_poweroff_noirq;
+ pm_ops->restore_noirq = platform_pm_restore_noirq;
+}
#else
#define USE_PLATFORM_PM_SLEEP_OPS
-#endif
+
+static inline void add_platform_pm_sleep_ops(struct dev_pm_ops *pm_ops) {}
+#endif /* CONFIG_PM_SLEEP */
#endif /* _PLATFORM_DEVICE_H_ */
On Sat, Apr 30, 2011 at 02:54:46AM +0200, Rafael J. Wysocki wrote:
> From: Rafael J. Wysocki <[email protected]>
>
> Introcude common headers, helper functions and callbacks allowing
> platforms to use simple generic power domains for runtime power
> management.
>
> Introduce struct generic_power_domain to be used for representing
> power domains that each contain a number of devices and may be
> master 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_on() is
> supposed to remove power from the entire power domain
> and ->power_off() 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.
>
> Signed-off-by: Rafael J. Wysocki <[email protected]>
> Acked-by: Greg Kroah-Hartman <[email protected]>
> ---
>
> Hi,
>
> This version of the patch fixes a bug that caused pm_runtime_suspend()
> (and equivalent) to return -EBUSY erroneously when suspending the last
> active device in a power domain. It has been tested on an ARM shmobile
> system.
>
> Greg, I hope your ACK is still valid. :-)
Yes, it is.
From: Rafael J. Wysocki <[email protected]>
Use the power domains support code added by the previous
patches to implement support for power domain A4MP on SH7372.
Signed-off-by: Rafael J. Wysocki <[email protected]>
---
Hi,
This patch is on top of https://lkml.org/lkml/2011/4/29/490 .
Thanks,
Rafael
---
arch/arm/mach-shmobile/board-mackerel.c | 2 ++
arch/arm/mach-shmobile/include/mach/sh7372.h | 3 +++
arch/arm/mach-shmobile/pm-sh7372.c | 7 +++++++
3 files changed, 12 insertions(+)
Index: linux-2.6/arch/arm/mach-shmobile/pm-sh7372.c
===================================================================
--- linux-2.6.orig/arch/arm/mach-shmobile/pm-sh7372.c
+++ linux-2.6/arch/arm/mach-shmobile/pm-sh7372.c
@@ -90,9 +90,16 @@ static struct sh7372_domain_data sh7372_
struct generic_power_domain sh7372_a4lc_domain;
+static struct sh7372_domain_data sh7372_a4mp_domain_data = {
+ .bit_shift = 2,
+};
+
+struct generic_power_domain sh7372_a4mp_domain;
+
static int __init sh7372_power_domains_init(void)
{
sh7372_init_domain(&sh7372_a4lc_domain, &sh7372_a4lc_domain_data);
+ sh7372_init_domain(&sh7372_a4mp_domain, &sh7372_a4mp_domain_data);
return 0;
}
core_initcall(sh7372_power_domains_init);
Index: linux-2.6/arch/arm/mach-shmobile/include/mach/sh7372.h
===================================================================
--- linux-2.6.orig/arch/arm/mach-shmobile/include/mach/sh7372.h
+++ linux-2.6/arch/arm/mach-shmobile/include/mach/sh7372.h
@@ -475,11 +475,14 @@ struct platform_device;
#ifdef CONFIG_PM
extern struct generic_power_domain sh7372_a4lc_domain;
#define SH7372_A4LC (&sh7372_a4lc_domain)
+extern struct generic_power_domain sh7372_a4mp_domain;
+#define SH7372_A4MP (&sh7372_a4mp_domain)
extern void sh7372_add_device_to_domain(struct generic_power_domain *domain,
struct platform_device *pdev);
#else
#define SH7372_A4LC NULL
+#define SH7372_A4PM NULL
static inline void sh7372_add_device_to_domain(struct generic_power_domain *dom,
struct platform_device *pd) {}
Index: linux-2.6/arch/arm/mach-shmobile/board-mackerel.c
===================================================================
--- linux-2.6.orig/arch/arm/mach-shmobile/board-mackerel.c
+++ linux-2.6/arch/arm/mach-shmobile/board-mackerel.c
@@ -1218,6 +1218,8 @@ 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();
}
On Sat, Apr 30, 2011 at 5:11 AM, Rafael J. Wysocki <[email protected]> wrote:
>
> Well, not really. ?There are a few things to consider.
>
> First, in general, there may be devices that have a real parent and belong
> to a power domain at the same time, so we can't "steal" the parent
> pointers from them.
Ah. right we only have one parent per device. Ok, setting a power
domain as a device's parent is not going to work for some devices.
However, I have some other questions.
1. pm_genpd_runtime_suspend and pm_genpd_runtime_resume are opened to
outside (not static and "extern"ed). Are devices supposed to call
pm_genpd_runtime_* directly? Shouldn't they be hidden so that devices
are forced to turn on/off power domains with runtime_pm framework
only? Is there any reason to expose them?
2. If we can assure that related clocks are not turned on when a
power domain is shutting down, it'd be nice.
I guess it would be sufficient to let it "WARN" at
gov->power_down_ok(). Is it the intention of governor?
Thank you! I also think this is going to help us a lot, too :)
- MyungJoo
--
MyungJoo Ham, Ph.D.
Mobile Software Platform Lab,
Digital Media and Communications (DMC) Business
Samsung Electronics
cell: 82-10-6714-2858
On Wednesday, May 04, 2011, MyungJoo Ham wrote:
> On Sat, Apr 30, 2011 at 5:11 AM, Rafael J. Wysocki <[email protected]> wrote:
> >
> > Well, not really. There are a few things to consider.
> >
> > First, in general, there may be devices that have a real parent and belong
> > to a power domain at the same time, so we can't "steal" the parent
> > pointers from them.
>
> Ah. right we only have one parent per device. Ok, setting a power
> domain as a device's parent is not going to work for some devices.
>
>
> However, I have some other questions.
>
> 1. pm_genpd_runtime_suspend and pm_genpd_runtime_resume are opened to
> outside (not static and "extern"ed). Are devices supposed to call
> pm_genpd_runtime_* directly?
I suppose you mean drivers. No, they aren't.
> Shouldn't they be hidden so that devices
> are forced to turn on/off power domains with runtime_pm framework
> only? Is there any reason to expose them?
I thought some subsystems might bypass pm_genpd_init() and use their own
initialization, in which case the extern definitions would be useful.
That said, they aren't necessary at the moment, so I'll make them static
in the final version of the patch.
> 2. If we can assure that related clocks are not turned on when a
> power domain is shutting down, it'd be nice.
If you look at the shmobile patch at
https://patchwork.kernel.org/patch/742932/
you'll see that it uses the ->stop_device() and ->start_device()
callbacks for this purpose.
> I guess it would be sufficient to let it "WARN" at
> gov->power_down_ok().
Sure, you can do that too.
> Is it the intention of governor?
No, the intention of the governor is to make it possible to take
for example, latencies, into account when deciding whether or not to
power down the domain. In short, my idea is that the governor will
have information on how much time it's going to take to power down
and resume the power domain (along with all devices) and, on the other hand,
what maximum wakeup latency is acceptable for the given power domain.
It will compare the two numbers and return "true" if the second one is
greater, for example. That said, I don't have a clean use case at the moment,
so that's more of a future stub than something necessary right now.
> Thank you! I also think this is going to help us a lot, too :)
Good to hear that. :-)
Thanks,
Rafael
On Friday, April 29, 2011, Rafael J. Wysocki wrote:
Hi,
This is an update of the patchset adding support for generic I/O power domains.
The first patch and the last two patches have been posted already and
discussed, the remaining two are new.
The patches are on top of the branch at:
git://git.kernel.org/pub/scm/linux/kernel/git/rafael/suspend-2.6.git power-domains
[1/5] - Add support for generic I/O power domains (runtime PM).
[2/5] - Introduce generic prepare and complete callbacks for system-wide
power transitions.
[3/5] - Add PM sleep support to the generic I/O power domains code introduced
in [1/5]
[4/5] - Use the new code to add support for I/O power domains on the SH7372 SoC.
[5/5] - Add support for power domain A4MP on SH7372.
Please let me know what you think.
Thanks,
Rafael
From: Rafael J. Wysocki <[email protected]>
Introduce common headers, helper functions and callbacks allowing
platforms to use simple generic power domains for runtime power
management.
Introduce struct generic_power_domain to be used for representing
power domains that each contain a number of devices and may be
master 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_on() is
supposed to remove power from the entire power domain
and ->power_off() 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.
Signed-off-by: Rafael J. Wysocki <[email protected]>
Acked-by: Greg Kroah-Hartman <[email protected]>
---
drivers/base/power/Makefile | 2
drivers/base/power/domain.c | 444 ++++++++++++++++++++++++++++++++++++++++++++
include/linux/pm.h | 3
include/linux/pm_domain.h | 75 +++++++
4 files changed, 522 insertions(+), 2 deletions(-)
Index: linux-2.6/include/linux/pm_domain.h
===================================================================
--- /dev/null
+++ linux-2.6/include/linux/pm_domain.h
@@ -0,0 +1,75 @@
+/*
+ * pm_domain.h - Definitions and headers related to device power domains.
+ *
+ * Copyright (C) 2011 Rafael J. Wysocki <[email protected]>, Renesas Electronics Corp.
+ *
+ * This file is released under the GPLv2.
+ */
+
+#ifndef _LINUX_PM_DOMAIN_H
+#define _LINUX_PM_DOMAIN_H
+
+#include <linux/device.h>
+
+struct dev_power_governor {
+ bool (*power_down_ok)(struct dev_power_domain *domain);
+};
+
+struct generic_power_domain {
+ struct dev_power_domain domain;
+ struct list_head node;
+ struct generic_power_domain *master;
+ struct list_head subdomain_list;
+ struct list_head device_list;
+ struct mutex lock;
+ struct dev_power_governor *gov;
+ unsigned int in_progress;
+ bool power_is_off;
+ int (*power_off)(struct dev_power_domain *domain);
+ int (*power_on)(struct dev_power_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;
+};
+
+#ifdef CONFIG_PM
+extern int pm_genpd_add_device(struct generic_power_domain *genpd,
+ struct device *dev);
+extern int pm_genpd_remove_device(struct generic_power_domain *genpd,
+ struct device *dev);
+extern int pm_genpd_add_subdomain(struct generic_power_domain *genpd,
+ struct generic_power_domain *new_subdomain);
+extern int pm_genpd_remove_subdomain(struct generic_power_domain *genpd,
+ struct generic_power_domain *target);
+extern void pm_genpd_init(struct generic_power_domain *genpd,
+ struct dev_power_governor *gov, bool is_off);
+#else
+static inline int pm_genpd_add_device(struct generic_power_domain *genpd,
+ struct device *dev)
+{
+ return -ENOSYS;
+}
+static inline int pm_genpd_remove_device(struct generic_power_domain *genpd,
+ struct device *dev)
+{
+ return -ENOSYS;
+}
+static inline int pm_genpd_add_subdomain(struct generic_power_domain *genpd,
+ struct generic_power_domain *new_sd)
+{
+ return -ENOSYS;
+}
+static inline int pm_genpd_remove_subdomain(struct generic_power_domain *genpd,
+ struct generic_power_domain *target)
+{
+ return -ENOSYS;
+}
+static inline void pm_genpd_init(struct generic_power_domain *genpd,
+ struct dev_power_governor *gov, bool is_off) {}
+#endif
+
+#endif /* _LINUX_PM_DOMAIN_H */
Index: linux-2.6/include/linux/pm.h
===================================================================
--- linux-2.6.orig/include/linux/pm.h
+++ linux-2.6/include/linux/pm.h
@@ -472,7 +472,8 @@ extern void update_pm_runtime_accounting
* subsystem-level and driver-level callbacks.
*/
struct dev_power_domain {
- struct dev_pm_ops ops;
+ struct dev_pm_ops ops;
+ void *platform_data;
};
/*
Index: linux-2.6/drivers/base/power/Makefile
===================================================================
--- linux-2.6.orig/drivers/base/power/Makefile
+++ linux-2.6/drivers/base/power/Makefile
@@ -1,4 +1,4 @@
-obj-$(CONFIG_PM) += sysfs.o generic_ops.o
+obj-$(CONFIG_PM) += sysfs.o generic_ops.o domain.o
obj-$(CONFIG_PM_SLEEP) += main.o wakeup.o
obj-$(CONFIG_PM_RUNTIME) += runtime.o
obj-$(CONFIG_PM_TRACE_RTC) += trace.o
Index: linux-2.6/drivers/base/power/domain.c
===================================================================
--- /dev/null
+++ linux-2.6/drivers/base/power/domain.c
@@ -0,0 +1,444 @@
+/*
+ * drivers/base/power/domain.c - Common code related to device power domains.
+ *
+ * Copyright (C) 2011 Rafael J. Wysocki <[email protected]>, Renesas Electronics Corp.
+ *
+ * This file is released under the GPLv2.
+ */
+
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/io.h>
+#include <linux/pm_runtime.h>
+#include <linux/pm_domain.h>
+#include <linux/slab.h>
+#include <linux/err.h>
+
+#ifdef CONFIG_PM_RUNTIME
+
+/**
+ * __pm_genpd_restore_device - Restore a pre-suspend state of a device.
+ * @dev: Device to restore the state of.
+ * @genpd: Power domain the device belongs to.
+ */
+static void __pm_genpd_restore_device(struct device *dev,
+ struct generic_power_domain *genpd)
+{
+ struct device_driver *drv = dev->driver;
+
+ if (genpd->start_device)
+ genpd->start_device(dev);
+
+ if (drv && drv->pm && drv->pm->runtime_resume)
+ drv->pm->runtime_resume(dev);
+
+ if (genpd->stop_device)
+ genpd->stop_device(dev);
+}
+
+/**
+ * __pm_genpd_poweroff - Remove power from a given power domain.
+ * @genpd: Power 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_power_domain *genpd)
+{
+ struct generic_power_domain *subdomain;
+ struct dev_list_entry *dle;
+ unsigned int not_suspended;
+ int ret;
+
+ if (genpd->power_is_off)
+ return 0;
+
+ not_suspended = 0;
+ list_for_each_entry(dle, &genpd->device_list, node)
+ if (!pm_runtime_suspended(dle->dev))
+ not_suspended++;
+
+ if (not_suspended > genpd->in_progress)
+ return -EBUSY;
+
+ list_for_each_entry_reverse(subdomain, &genpd->subdomain_list, node) {
+ mutex_lock(&subdomain->lock);
+ ret = __pm_genpd_poweroff(subdomain);
+ mutex_unlock(&subdomain->lock);
+ if (ret)
+ return ret;
+ }
+
+ 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->device_list, node) {
+ struct device *dev = dle->dev;
+ struct device_driver *drv = dev->driver;
+
+ if (genpd->start_device)
+ genpd->start_device(dev);
+
+ if (drv && drv->pm && drv->pm->runtime_suspend)
+ ret = drv->pm->runtime_suspend(dev);
+
+ if (genpd->stop_device)
+ genpd->stop_device(dev);
+
+ if (ret)
+ goto err_dev;
+ }
+
+ if (genpd->power_off)
+ genpd->power_off(&genpd->domain);
+
+ genpd->power_is_off = true;
+
+ return 0;
+
+ err_dev:
+ list_for_each_entry_continue(dle, &genpd->device_list, node)
+ __pm_genpd_restore_device(dle->dev, genpd);
+
+ return ret;
+}
+
+/**
+ * pm_genpd_poweroff - Remove power from a given power domain and its masters.
+ * @genpd: Power domain to power down.
+ *
+ * Try to remove power from @genpd and all of its masters in order to save as
+ * much power as possible.
+ */
+static void pm_genpd_poweroff(struct generic_power_domain *genpd)
+{
+ struct generic_power_domain *master;
+
+ mutex_lock(&genpd->lock);
+ master = genpd->master;
+ if (master) {
+ mutex_unlock(&genpd->lock);
+ pm_genpd_poweroff(master);
+ return;
+ }
+ __pm_genpd_poweroff(genpd);
+ mutex_unlock(&genpd->lock);
+}
+
+/**
+ * pm_genpd_runtime_suspend - Suspend a device belonging to I/O power domain.
+ * @dev: Device to suspend.
+ *
+ * Carry out a runtime suspend of a device under the assumption that its
+ * pwr_domain field points to the domain member of an object of type
+ * struct generic_power_domain representing a power domain consisting of I/O
+ * devices.
+ */
+static int pm_genpd_runtime_suspend(struct device *dev)
+{
+ struct generic_power_domain *genpd, *master;
+
+ dev_dbg(dev, "%s()\n", __func__);
+
+ if (IS_ERR_OR_NULL(dev->pwr_domain))
+ return -EINVAL;
+
+ genpd = container_of(dev->pwr_domain,
+ struct generic_power_domain, domain);
+
+ mutex_lock(&genpd->lock);
+
+ if (genpd->stop_device) {
+ int ret = genpd->stop_device(dev);
+ if (ret)
+ goto out;
+ }
+ genpd->in_progress++;
+
+ master = genpd->master;
+ if (master) {
+ mutex_unlock(&genpd->lock);
+
+ pm_genpd_poweroff(master);
+
+ mutex_lock(&genpd->lock);
+ } else {
+ __pm_genpd_poweroff(genpd);
+ }
+
+ genpd->in_progress--;
+
+ out:
+ mutex_unlock(&genpd->lock);
+
+ return 0;
+}
+
+/**
+ * __pm_genpd_poweron - Restore power for a given power domain.
+ * @genpd: Power domain to power up.
+ *
+ * Restore power for @genpd and run runtime resume callbacks provided by all of
+ * its devices' drivers.
+ */
+static int __pm_genpd_poweron(struct generic_power_domain *genpd)
+{
+ struct dev_list_entry *dle;
+
+ if (!genpd->power_is_off)
+ return 0;
+
+ if (genpd->power_on) {
+ int ret = genpd->power_on(&genpd->domain);
+ if (ret)
+ return ret;
+ }
+
+ genpd->power_is_off = false;
+
+ list_for_each_entry(dle, &genpd->device_list, node)
+ __pm_genpd_restore_device(dle->dev, genpd);
+
+ return 0;
+}
+
+/**
+ * pm_genpd_poweron - Restore power for a given power domain and its masters.
+ * @genpd: Power domain to power up.
+ *
+ * Restore power for @genpd and all of its masters so that it is possible to
+ * resume a device belonging to it.
+ */
+static int pm_genpd_poweron(struct generic_power_domain *genpd)
+{
+ struct generic_power_domain *master;
+ int ret;
+
+ mutex_lock(&genpd->lock);
+ master = genpd->master;
+ if (master) {
+ mutex_unlock(&genpd->lock);
+
+ ret = pm_genpd_poweron(master);
+ if (ret)
+ return ret;
+
+ mutex_lock(&genpd->lock);
+ }
+ ret = __pm_genpd_poweron(genpd);
+ mutex_unlock(&genpd->lock);
+
+ return ret;
+}
+
+/**
+ * pm_genpd_runtime_resume - Resume a device belonging to I/O power domain.
+ * @dev: Device to resume.
+ *
+ * Carry out a runtime resume of a device under the assumption that its
+ * pwr_domain field points to the domain member of an object of type
+ * struct generic_power_domain representing a power domain consisting of I/O
+ * devices.
+ */
+static int pm_genpd_runtime_resume(struct device *dev)
+{
+ struct generic_power_domain *genpd;
+ int ret;
+
+ dev_dbg(dev, "%s()\n", __func__);
+
+ if (IS_ERR_OR_NULL(dev->pwr_domain))
+ return -EINVAL;
+
+ genpd = container_of(dev->pwr_domain,
+ struct generic_power_domain, domain);
+
+ ret = pm_genpd_poweron(genpd);
+ if (ret)
+ return ret;
+
+ if (genpd->start_device)
+ genpd->start_device(dev);
+
+ return 0;
+}
+
+#else
+
+#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 power domain.
+ * @genpd: Power domain to add the device to.
+ * @dev: Device to be added.
+ */
+int pm_genpd_add_device(struct generic_power_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);
+
+ list_for_each_entry(dle, &genpd->device_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;
+ list_add_tail(&dle->node, &genpd->device_list);
+
+ spin_lock_irq(&dev->power.lock);
+ dev->pwr_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 power domain.
+ * @genpd: Power domain to remove the device from.
+ * @dev: Device to be removed.
+ */
+int pm_genpd_remove_device(struct generic_power_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->device_list, node) {
+ if (dle->dev != dev)
+ continue;
+
+ spin_lock_irq(&dev->power.lock);
+ dev->pwr_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 power domain.
+ * @genpd: Master power domain to add the subdomain to.
+ * @new_subdomain: Subdomain to be added.
+ */
+int pm_genpd_add_subdomain(struct generic_power_domain *genpd,
+ struct generic_power_domain *new_subdomain)
+{
+ struct generic_power_domain *subdomain;
+ int ret = 0;
+
+ if (IS_ERR_OR_NULL(genpd) || IS_ERR_OR_NULL(new_subdomain))
+ return -EINVAL;
+
+ mutex_lock(&genpd->lock);
+
+ list_for_each_entry(subdomain, &genpd->subdomain_list, node)
+ if (subdomain == new_subdomain) {
+ ret = -EINVAL;
+ goto out;
+ }
+
+ mutex_lock(&new_subdomain->lock);
+ list_add_tail(&new_subdomain->node, &genpd->subdomain_list);
+ new_subdomain->master = genpd;
+ mutex_unlock(&new_subdomain->lock);
+
+ out:
+ mutex_unlock(&genpd->lock);
+
+ return ret;
+}
+
+/**
+ * pm_genpd_remove_subdomain - Remove a subdomain from an I/O power domain.
+ * @genpd: Master power domain to remove the subdomain from.
+ * @target: Subdomain to be removed.
+ */
+int pm_genpd_remove_subdomain(struct generic_power_domain *genpd,
+ struct generic_power_domain *target)
+{
+ struct generic_power_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->subdomain_list, node) {
+ if (subdomain != target)
+ continue;
+
+ mutex_lock(&subdomain->lock);
+ list_del(&subdomain->node);
+ subdomain->master = NULL;
+ mutex_unlock(&subdomain->lock);
+
+ ret = 0;
+ break;
+ }
+
+ mutex_unlock(&genpd->lock);
+
+ return ret;
+}
+
+/**
+ * pm_genpd_init - Initialize an I/O power domain object.
+ * @genpd: Power domain object to initialize.
+ */
+void pm_genpd_init(struct generic_power_domain *genpd,
+ struct dev_power_governor *gov, bool is_off)
+{
+ if (IS_ERR_OR_NULL(genpd))
+ return;
+
+ INIT_LIST_HEAD(&genpd->node);
+ genpd->master = NULL;
+ INIT_LIST_HEAD(&genpd->device_list);
+ INIT_LIST_HEAD(&genpd->subdomain_list);
+ mutex_init(&genpd->lock);
+ genpd->gov = gov;
+ genpd->in_progress = 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;
+}
From: Rafael J. Wysocki <[email protected]>
Introduce generic ->prepare() and ->complete() power management
callbacks that can be used by subsystems and power domains, export
them and make the generic_subsys_pm_ops object point to them.
Additionally, provide NULL definitions of all the generic system
sleep callbacks for CONFIG_PM_SLEEP unset.
Signed-off-by: Rafael J. Wysocki <[email protected]>
---
drivers/base/power/generic_ops.c | 39 +++++++++++++++++++++++++++++++++++++++
include/linux/pm.h | 26 +++++++++++++++++++-------
2 files changed, 58 insertions(+), 7 deletions(-)
Index: linux-2.6/drivers/base/power/generic_ops.c
===================================================================
--- linux-2.6.orig/drivers/base/power/generic_ops.c
+++ linux-2.6/drivers/base/power/generic_ops.c
@@ -74,6 +74,23 @@ EXPORT_SYMBOL_GPL(pm_generic_runtime_res
#ifdef CONFIG_PM_SLEEP
/**
+ * pm_generic_prepare - Generic routine preparing a device for power transition.
+ * @dev: Device to prepare.
+ *
+ * Prepare a device for a system-wide power transition.
+ */
+int pm_generic_prepare(struct device *dev)
+{
+ struct device_driver *drv = dev->driver;
+ int ret = 0;
+
+ if (drv && drv->pm && drv->pm->prepare)
+ ret = drv->pm->prepare(dev);
+
+ return ret;
+}
+
+/**
* __pm_generic_call - Generic suspend/freeze/poweroff/thaw subsystem callback.
* @dev: Device to handle.
* @event: PM transition of the system under way.
@@ -213,16 +230,38 @@ int pm_generic_restore(struct device *de
return __pm_generic_resume(dev, PM_EVENT_RESTORE);
}
EXPORT_SYMBOL_GPL(pm_generic_restore);
+
+/**
+ * pm_generic_complete - Generic routine competing a device power transition.
+ * @dev: Device to handle.
+ *
+ * Complete a device power transition during a system-wide power transition.
+ */
+void pm_generic_complete(struct device *dev)
+{
+ struct device_driver *drv = dev->driver;
+
+ if (drv && drv->pm && drv->pm->complete)
+ drv->pm->complete(dev);
+
+ /*
+ * Let runtime PM try to suspend devices that haven't been in use before
+ * going into the system-wide sleep state we're resuming from.
+ */
+ pm_runtime_idle(dev);
+}
#endif /* CONFIG_PM_SLEEP */
struct dev_pm_ops generic_subsys_pm_ops = {
#ifdef CONFIG_PM_SLEEP
+ .prepare = pm_generic_prepare,
.suspend = pm_generic_suspend,
.resume = pm_generic_resume,
.freeze = pm_generic_freeze,
.thaw = pm_generic_thaw,
.poweroff = pm_generic_poweroff,
.restore = pm_generic_restore,
+ .complete = pm_generic_complete,
#endif
#ifdef CONFIG_PM_RUNTIME
.runtime_suspend = pm_generic_runtime_suspend,
Index: linux-2.6/include/linux/pm.h
===================================================================
--- linux-2.6.orig/include/linux/pm.h
+++ linux-2.6/include/linux/pm.h
@@ -547,6 +547,16 @@ extern void __suspend_report_result(cons
} while (0)
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(struct device *dev);
+extern int pm_generic_resume(struct device *dev);
+extern int pm_generic_freeze(struct device *dev);
+extern int pm_generic_thaw(struct device *dev);
+extern int pm_generic_restore(struct device *dev);
+extern int pm_generic_poweroff(struct device *dev);
+extern void pm_generic_complete(struct device *dev);
+
#else /* !CONFIG_PM_SLEEP */
#define device_pm_lock() do {} while (0)
@@ -563,6 +573,15 @@ static inline int device_pm_wait_for_dev
{
return 0;
}
+
+#define pm_generic_prepare NULL
+#define pm_generic_suspend NULL
+#define pm_generic_resume NULL
+#define pm_generic_freeze NULL
+#define pm_generic_thaw NULL
+#define pm_generic_restore NULL
+#define pm_generic_poweroff NULL
+#define pm_generic_complete NULL
#endif /* !CONFIG_PM_SLEEP */
/* How to reorder dpm_list after device_move() */
@@ -573,11 +592,4 @@ enum dpm_order {
DPM_ORDER_DEV_LAST,
};
-extern int pm_generic_suspend(struct device *dev);
-extern int pm_generic_resume(struct device *dev);
-extern int pm_generic_freeze(struct device *dev);
-extern int pm_generic_thaw(struct device *dev);
-extern int pm_generic_restore(struct device *dev);
-extern int pm_generic_poweroff(struct device *dev);
-
#endif /* _LINUX_PM_H */
From: Rafael J. Wysocki <[email protected]>
Make generic power domains support system-wide power transitions
(system suspend and hibernation). Add suspend, resume, freeze and
thaw callbacks to be associated with struct generic_power_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
power domain ->suspend_noirq() callback stops the device and
eventually removes power from the power domain it belongs to (after
all devices in the power domain have been stopped). During system
resume the power domain ->resume_noirq() callback powers up the power
domain (when executed for the first time for the given power domain)
and starts the device, 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 <[email protected]>
---
drivers/base/power/domain.c | 292 ++++++++++++++++++++++++++++++++++++++++++++
include/linux/pm_domain.h | 2
2 files changed, 294 insertions(+)
Index: linux-2.6/drivers/base/power/domain.c
===================================================================
--- linux-2.6.orig/drivers/base/power/domain.c
+++ linux-2.6/drivers/base/power/domain.c
@@ -273,6 +273,280 @@ static int pm_genpd_runtime_resume(struc
#endif /* CONFIG_PM_RUNTIME */
+#ifdef CONFIG_PM_SLEEP
+
+/**
+ * pm_genpd_powered_down - Check if power has been removed from a power domain.
+ * @genpd: Power domain to check.
+ */
+static bool pm_genpd_powered_down(struct generic_power_domain *genpd)
+{
+ bool ret;
+
+ mutex_lock(&genpd->lock);
+ ret = genpd->power_is_off;
+ mutex_unlock(&genpd->lock);
+
+ return ret;
+}
+
+/**
+ * pm_genpd_suspend - Suspend a device belonging to an I/O power domain.
+ * @dev: Device to suspend.
+ *
+ * Suspend a device under the assumption that its pwr_domain field points to the
+ * domain member of an object of type struct generic_power_domain representing
+ * a power domain consisting of I/O devices.
+ */
+static int pm_genpd_suspend(struct device *dev)
+{
+ struct generic_power_domain *genpd;
+
+ dev_dbg(dev, "%s()\n", __func__);
+
+ if (IS_ERR_OR_NULL(dev->pwr_domain))
+ return -EINVAL;
+
+ genpd = container_of(dev->pwr_domain,
+ struct generic_power_domain, domain);
+
+ if (pm_genpd_powered_down(genpd))
+ return 0;
+
+ /*
+ * If the device is in the (runtime) "suspended" state, call
+ * ->start_device() for it, if defined.
+ */
+ pm_runtime_resume(dev);
+
+ return pm_generic_suspend(dev);
+}
+
+/**
+ * pm_genpd_suspend_noirq - Late suspend of a device from an I/O power domain.
+ * @dev: Device to suspend.
+ *
+ * Carry out a late suspend of a device under the assumption that its
+ * pwr_domain field points to the domain member of an object of type
+ * struct generic_power_domain representing a power domain consisting of I/O
+ * devices.
+ */
+static int pm_genpd_suspend_noirq(struct device *dev)
+{
+ struct generic_power_domain *genpd;
+
+ dev_dbg(dev, "%s()\n", __func__);
+
+ if (IS_ERR_OR_NULL(dev->pwr_domain))
+ return -EINVAL;
+
+ genpd = container_of(dev->pwr_domain,
+ struct generic_power_domain, domain);
+
+ if (pm_genpd_powered_down(genpd))
+ return 0;
+
+ if (genpd->stop_device)
+ genpd->stop_device(dev);
+
+ mutex_lock(&genpd->lock);
+ if (++genpd->suspended_count == genpd->device_count) {
+ if (genpd->power_off)
+ genpd->power_off(&genpd->domain);
+ }
+ mutex_unlock(&genpd->lock);
+
+ 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
+ * pwr_domain field points to the domain member of an object of type
+ * struct generic_power_domain representing a power domain consisting of I/O
+ * devices.
+ */
+static int pm_genpd_resume_noirq(struct device *dev)
+{
+ struct generic_power_domain *genpd;
+
+ dev_dbg(dev, "%s()\n", __func__);
+
+ if (IS_ERR_OR_NULL(dev->pwr_domain))
+ return -EINVAL;
+
+ genpd = container_of(dev->pwr_domain,
+ struct generic_power_domain, domain);
+
+ if (pm_genpd_powered_down(genpd))
+ return 0;
+
+ mutex_lock(&genpd->lock);
+ if (genpd->suspended_count == genpd->device_count) {
+ if (genpd->power_on) {
+ int ret = genpd->power_on(&genpd->domain);
+ if (ret) {
+ mutex_unlock(&genpd->lock);
+ return ret;
+ }
+ }
+ }
+ genpd->suspended_count--;
+ mutex_unlock(&genpd->lock);
+
+ if (genpd->start_device)
+ genpd->start_device(dev);
+
+ return 0;
+}
+
+/**
+ * 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 pwr_domain field points to the
+ * domain member of an object of type struct generic_power_domain representing
+ * a power domain consisting of I/O devices.
+ */
+static int pm_genpd_resume(struct device *dev)
+{
+ struct generic_power_domain *genpd;
+
+ dev_dbg(dev, "%s()\n", __func__);
+
+ if (IS_ERR_OR_NULL(dev->pwr_domain))
+ return -EINVAL;
+
+ genpd = container_of(dev->pwr_domain,
+ struct generic_power_domain, domain);
+
+ return pm_genpd_powered_down(genpd) ? 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 pwr_domain field points to the
+ * domain member of an object of type struct generic_power_domain representing
+ * a power domain consisting of I/O devices.
+ */
+static int pm_genpd_freeze(struct device *dev)
+{
+ struct generic_power_domain *genpd;
+
+ dev_dbg(dev, "%s()\n", __func__);
+
+ if (IS_ERR_OR_NULL(dev->pwr_domain))
+ return -EINVAL;
+
+ genpd = container_of(dev->pwr_domain,
+ struct generic_power_domain, domain);
+
+ if (pm_genpd_powered_down(genpd))
+ return 0;
+
+ /*
+ * If the device is in the (runtime) "suspended" state, call
+ * ->start_device() for it, if defined.
+ */
+ pm_runtime_resume(dev);
+
+ return 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
+ * pwr_domain field points to the domain member of an object of type
+ * struct generic_power_domain representing a power domain consisting of I/O
+ * devices.
+ */
+static int pm_genpd_freeze_noirq(struct device *dev)
+{
+ struct generic_power_domain *genpd;
+
+ dev_dbg(dev, "%s()\n", __func__);
+
+ if (IS_ERR_OR_NULL(dev->pwr_domain))
+ return -EINVAL;
+
+ genpd = container_of(dev->pwr_domain,
+ struct generic_power_domain, domain);
+
+ if (!pm_genpd_powered_down(genpd) && 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
+ * pwr_domain field points to the domain member of an object of type
+ * struct generic_power_domain representing a power domain consisting of I/O
+ * devices.
+ */
+static int pm_genpd_thaw_noirq(struct device *dev)
+{
+ struct generic_power_domain *genpd;
+
+ dev_dbg(dev, "%s()\n", __func__);
+
+ if (IS_ERR_OR_NULL(dev->pwr_domain))
+ return -EINVAL;
+
+ genpd = container_of(dev->pwr_domain,
+ struct generic_power_domain, domain);
+
+ if (!pm_genpd_powered_down(genpd) && genpd->start_device)
+ genpd->start_device(dev);
+
+ return 0;
+}
+
+/**
+ * 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 pwr_domain field points to the
+ * domain member of an object of type struct generic_power_domain representing
+ * a power domain consisting of I/O devices.
+ */
+static int pm_genpd_thaw(struct device *dev)
+{
+ struct generic_power_domain *genpd;
+
+ dev_dbg(dev, "%s()\n", __func__);
+
+ if (IS_ERR_OR_NULL(dev->pwr_domain))
+ return -EINVAL;
+
+ genpd = container_of(dev->pwr_domain,
+ struct generic_power_domain, domain);
+
+ return pm_genpd_powered_down(genpd) ? 0 : pm_generic_thaw(dev);
+}
+
+#else
+
+#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
+
+#endif /* CONFIG_PM_SLEEP */
+
/**
* pm_genpd_add_device - Add a device to an I/O power domain.
* @genpd: Power domain to add the device to.
@@ -304,6 +578,7 @@ int pm_genpd_add_device(struct generic_p
dle->dev = dev;
list_add_tail(&dle->node, &genpd->device_list);
+ genpd->device_count++;
spin_lock_irq(&dev->power.lock);
dev->pwr_domain = &genpd->domain;
@@ -341,6 +616,7 @@ int pm_genpd_remove_device(struct generi
dev->pwr_domain = NULL;
spin_unlock_irq(&dev->power.lock);
+ genpd->device_count--;
list_del(&dle->node);
kfree(dle);
@@ -438,7 +714,23 @@ void pm_genpd_init(struct generic_power_
genpd->gov = gov;
genpd->in_progress = 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_generic_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_suspend;
+ genpd->domain.ops.poweroff_noirq = pm_genpd_suspend_noirq;
+ genpd->domain.ops.restore_noirq = pm_genpd_resume_noirq;
+ genpd->domain.ops.restore = pm_genpd_resume;
+ genpd->domain.ops.complete = pm_generic_complete;
}
Index: linux-2.6/include/linux/pm_domain.h
===================================================================
--- linux-2.6.orig/include/linux/pm_domain.h
+++ linux-2.6/include/linux/pm_domain.h
@@ -25,6 +25,8 @@ struct generic_power_domain {
struct dev_power_governor *gov;
unsigned int in_progress;
bool power_is_off;
+ unsigned int device_count;
+ unsigned int suspended_count;
int (*power_off)(struct dev_power_domain *domain);
int (*power_on)(struct dev_power_domain *domain);
int (*start_device)(struct device *dev);
From: Rafael J. Wysocki <[email protected]>
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 <[email protected]>
---
arch/arm/mach-shmobile/Makefile | 4 +
arch/arm/mach-shmobile/board-mackerel.c | 3
arch/arm/mach-shmobile/include/mach/sh7372.h | 16 ++++
arch/arm/mach-shmobile/pm-sh7372.c | 97 +++++++++++++++++++++++++++
4 files changed, 120 insertions(+)
Index: linux-2.6/arch/arm/mach-shmobile/board-mackerel.c
===================================================================
--- linux-2.6.orig/arch/arm/mach-shmobile/board-mackerel.c
+++ linux-2.6/arch/arm/mach-shmobile/board-mackerel.c
@@ -1215,6 +1215,9 @@ static void __init mackerel_init(void)
platform_add_devices(mackerel_devices, ARRAY_SIZE(mackerel_devices));
+ sh7372_add_device_to_domain(SH7372_A4LC, &lcdc_device);
+ sh7372_add_device_to_domain(SH7372_A4LC, &hdmi_lcdc_device);
+
hdmi_init_pm_clock();
}
Index: linux-2.6/arch/arm/mach-shmobile/pm-sh7372.c
===================================================================
--- /dev/null
+++ linux-2.6/arch/arm/mach-shmobile/pm-sh7372.c
@@ -0,0 +1,97 @@
+/*
+ * arch/arm/mach-shmobile/pm-sh7372.c
+ *
+ * Power domains support code for SH7372
+ *
+ * Copyright (C) 2011 Rafael J. Wysocki <[email protected]>, Renesas Electronics Corp.
+ *
+ * This file is subject to the terms and conditions of the GNU General Public
+ * License v2. See the file "COPYING" in the main directory of this archive
+ * for more details.
+ */
+
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/io.h>
+#include <linux/pm_runtime.h>
+#include <linux/platform_device.h>
+
+#include <mach/sh7372.h>
+
+struct sh7372_domain_data {
+ unsigned int bit_shift;
+};
+
+#define SPDCR 0xe6180008
+#define SWUCR 0xe6180014
+#define PSTR 0xe6180080
+
+static int pd_power_down(struct dev_power_domain *domain)
+{
+ struct sh7372_domain_data *dd = domain->platform_data;
+ unsigned int mask = 1 << dd->bit_shift;
+
+ if (__raw_readl(PSTR) & mask) {
+ __raw_writel(mask, SPDCR);
+
+ while (__raw_readl(SPDCR) & mask) {}
+
+ pr_debug("sh7372 power domain down 0x%08x -> PSTR = 0x%08x\n",
+ mask, __raw_readl(PSTR));
+ }
+
+ return 0;
+}
+
+static int pd_power_up(struct dev_power_domain *domain)
+{
+ struct sh7372_domain_data *dd = domain->platform_data;
+ unsigned int mask = 1 << dd->bit_shift;
+
+ if (!(__raw_readl(PSTR) & mask)) {
+ __raw_writel(mask, SWUCR);
+
+ while (__raw_readl(SWUCR) & mask) {}
+
+ pr_debug("sh7372 power domain up 0x%08x -> PSTR = 0x%08x\n",
+ mask, __raw_readl(PSTR));
+ }
+
+ return 0;
+}
+
+static void sh7372_init_domain(struct generic_power_domain *domain,
+ struct sh7372_domain_data *pdata)
+{
+ pm_genpd_init(domain, NULL, false);
+ domain->domain.platform_data = pdata;
+ domain->stop_device = pm_runtime_clk_suspend;
+ domain->start_device = pm_runtime_clk_resume;
+ domain->power_off = pd_power_down;
+ domain->power_on = pd_power_up;
+}
+
+void sh7372_add_device_to_domain(struct generic_power_domain *domain,
+ struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+
+ if (!dev->power.subsys_data) {
+ pm_runtime_clk_init(dev);
+ pm_runtime_clk_add(dev, NULL);
+ }
+ pm_genpd_add_device(domain, dev);
+}
+
+static struct sh7372_domain_data sh7372_a4lc_domain_data = {
+ .bit_shift = 1,
+};
+
+struct generic_power_domain sh7372_a4lc_domain;
+
+static int __init sh7372_power_domains_init(void)
+{
+ sh7372_init_domain(&sh7372_a4lc_domain, &sh7372_a4lc_domain_data);
+ return 0;
+}
+core_initcall(sh7372_power_domains_init);
Index: linux-2.6/arch/arm/mach-shmobile/include/mach/sh7372.h
===================================================================
--- linux-2.6.orig/arch/arm/mach-shmobile/include/mach/sh7372.h
+++ linux-2.6/arch/arm/mach-shmobile/include/mach/sh7372.h
@@ -12,6 +12,7 @@
#define __ASM_SH7372_H__
#include <linux/sh_clk.h>
+#include <linux/pm_domain.h>
/*
* Pin Function Controller:
@@ -469,4 +470,19 @@ extern struct clk sh7372_fsibck_clk;
extern struct clk sh7372_fsidiva_clk;
extern struct clk sh7372_fsidivb_clk;
+struct platform_device;
+
+#ifdef CONFIG_PM
+extern struct generic_power_domain sh7372_a4lc_domain;
+#define SH7372_A4LC (&sh7372_a4lc_domain)
+
+extern void sh7372_add_device_to_domain(struct generic_power_domain *domain,
+ struct platform_device *pdev);
+#else
+#define SH7372_A4LC NULL
+
+static inline void sh7372_add_device_to_domain(struct generic_power_domain *dom,
+ struct platform_device *pd) {}
+#endif /* CONFIG_PM */
+
#endif /* __ASM_SH7372_H__ */
Index: linux-2.6/arch/arm/mach-shmobile/Makefile
===================================================================
--- linux-2.6.orig/arch/arm/mach-shmobile/Makefile
+++ linux-2.6/arch/arm/mach-shmobile/Makefile
@@ -40,6 +40,10 @@ obj-$(CONFIG_MACH_AP4EVB) += board-ap4ev
obj-$(CONFIG_MACH_AG5EVM) += board-ag5evm.o
obj-$(CONFIG_MACH_MACKEREL) += board-mackerel.o
+# PM objects
+pm-$(CONFIG_ARCH_SH7372) += pm-sh7372.o
+
# Framework support
obj-$(CONFIG_SMP) += $(smp-y)
obj-$(CONFIG_GENERIC_GPIO) += $(pfc-y)
+obj-$(CONFIG_PM) += $(pm-y)
From: Rafael J. Wysocki <[email protected]>
Use the power domains support code added by the previous
patches to implement support for power domain A4MP on SH7372.
Signed-off-by: Rafael J. Wysocki <[email protected]>
---
arch/arm/mach-shmobile/board-mackerel.c | 2 ++
arch/arm/mach-shmobile/include/mach/sh7372.h | 3 +++
arch/arm/mach-shmobile/pm-sh7372.c | 7 +++++++
3 files changed, 12 insertions(+)
Index: linux-2.6/arch/arm/mach-shmobile/pm-sh7372.c
===================================================================
--- linux-2.6.orig/arch/arm/mach-shmobile/pm-sh7372.c
+++ linux-2.6/arch/arm/mach-shmobile/pm-sh7372.c
@@ -89,9 +89,16 @@ static struct sh7372_domain_data sh7372_
struct generic_power_domain sh7372_a4lc_domain;
+static struct sh7372_domain_data sh7372_a4mp_domain_data = {
+ .bit_shift = 2,
+};
+
+struct generic_power_domain sh7372_a4mp_domain;
+
static int __init sh7372_power_domains_init(void)
{
sh7372_init_domain(&sh7372_a4lc_domain, &sh7372_a4lc_domain_data);
+ sh7372_init_domain(&sh7372_a4mp_domain, &sh7372_a4mp_domain_data);
return 0;
}
core_initcall(sh7372_power_domains_init);
Index: linux-2.6/arch/arm/mach-shmobile/include/mach/sh7372.h
===================================================================
--- linux-2.6.orig/arch/arm/mach-shmobile/include/mach/sh7372.h
+++ linux-2.6/arch/arm/mach-shmobile/include/mach/sh7372.h
@@ -475,11 +475,14 @@ struct platform_device;
#ifdef CONFIG_PM
extern struct generic_power_domain sh7372_a4lc_domain;
#define SH7372_A4LC (&sh7372_a4lc_domain)
+extern struct generic_power_domain sh7372_a4mp_domain;
+#define SH7372_A4MP (&sh7372_a4mp_domain)
extern void sh7372_add_device_to_domain(struct generic_power_domain *domain,
struct platform_device *pdev);
#else
#define SH7372_A4LC NULL
+#define SH7372_A4PM NULL
static inline void sh7372_add_device_to_domain(struct generic_power_domain *dom,
struct platform_device *pd) {}
Index: linux-2.6/arch/arm/mach-shmobile/board-mackerel.c
===================================================================
--- linux-2.6.orig/arch/arm/mach-shmobile/board-mackerel.c
+++ linux-2.6/arch/arm/mach-shmobile/board-mackerel.c
@@ -1218,6 +1218,8 @@ 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();
}
On Sun, 8 May 2011, Rafael J. Wysocki wrote:
> From: Rafael J. Wysocki <[email protected]>
>
> Make generic power domains support system-wide power transitions
> (system suspend and hibernation). Add suspend, resume, freeze and
> thaw callbacks to be associated with struct generic_power_domain
> objects and make pm_genpd_init() use them as appropriate.
...
> Index: linux-2.6/drivers/base/power/domain.c
> ===================================================================
> --- linux-2.6.orig/drivers/base/power/domain.c
> +++ linux-2.6/drivers/base/power/domain.c
> @@ -273,6 +273,280 @@ static int pm_genpd_runtime_resume(struc
>
> #endif /* CONFIG_PM_RUNTIME */
>
> +#ifdef CONFIG_PM_SLEEP
> +
> +/**
> + * pm_genpd_powered_down - Check if power has been removed from a power domain.
> + * @genpd: Power domain to check.
> + */
> +static bool pm_genpd_powered_down(struct generic_power_domain *genpd)
> +{
> + bool ret;
> +
> + mutex_lock(&genpd->lock);
> + ret = genpd->power_is_off;
> + mutex_unlock(&genpd->lock);
> +
> + return ret;
> +}
What is the purpose of the mutex? On the face of it, there's nothing
to prevent another thread from changing the domain's power state in
between the mutex_unlock() call and the return statement.
Alan Stern
On Monday, May 09, 2011, Alan Stern wrote:
> On Sun, 8 May 2011, Rafael J. Wysocki wrote:
>
> > From: Rafael J. Wysocki <[email protected]>
> >
> > Make generic power domains support system-wide power transitions
> > (system suspend and hibernation). Add suspend, resume, freeze and
> > thaw callbacks to be associated with struct generic_power_domain
> > objects and make pm_genpd_init() use them as appropriate.
>
> ...
>
> > Index: linux-2.6/drivers/base/power/domain.c
> > ===================================================================
> > --- linux-2.6.orig/drivers/base/power/domain.c
> > +++ linux-2.6/drivers/base/power/domain.c
> > @@ -273,6 +273,280 @@ static int pm_genpd_runtime_resume(struc
> >
> > #endif /* CONFIG_PM_RUNTIME */
> >
> > +#ifdef CONFIG_PM_SLEEP
> > +
> > +/**
> > + * pm_genpd_powered_down - Check if power has been removed from a power domain.
> > + * @genpd: Power domain to check.
> > + */
> > +static bool pm_genpd_powered_down(struct generic_power_domain *genpd)
> > +{
> > + bool ret;
> > +
> > + mutex_lock(&genpd->lock);
> > + ret = genpd->power_is_off;
> > + mutex_unlock(&genpd->lock);
> > +
> > + return ret;
> > +}
>
> What is the purpose of the mutex? On the face of it, there's nothing
> to prevent another thread from changing the domain's power state in
> between the mutex_unlock() call and the return statement.
Now, that I think of it, the mutex isn't really necessary.
genpd->power_is_off is only changed by the runtime PM code that will
never be called during system suspend if genpd->power_is_off is set
already.
I'm going to post an update shortly, I'll include a fix for that in it.
Thanks,
Rafael
From: Rafael J. Wysocki <[email protected]>
Make generic power domains support system-wide power transitions
(system suspend and hibernation). Add suspend, resume, freeze and
thaw callbacks to be associated with struct generic_power_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
power domain ->suspend_noirq() callback stops the device and
eventually removes power from the power domain it belongs to (after
all devices in the power domain have been stopped). During system
resume the power domain ->resume_noirq() callback powers up the power
domain (when executed for the first time for the given power domain)
and starts the device, 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 <[email protected]>
---
Hi,
In this version of the patch I added ->prepare() and ->complete() power domain
callbacks behaving as described in the changelog (ie. doing nothing if power
has been removed from the power domain). On the system I tested this patch
none of the drivers implements ->prepare() and ->complete(), so I didn't see
the problem before.
I also made the system-wide PM callbacks access genpd->power_is_off without
locking, because that test cannot race with the runtime PM code modifying
that field.
Thanks,
Rafael
---
drivers/base/power/domain.c | 328 ++++++++++++++++++++++++++++++++++++++++++++
include/linux/pm_domain.h | 2
2 files changed, 330 insertions(+)
Index: linux-2.6/drivers/base/power/domain.c
===================================================================
--- linux-2.6.orig/drivers/base/power/domain.c
+++ linux-2.6/drivers/base/power/domain.c
@@ -273,6 +273,316 @@ static int pm_genpd_runtime_resume(struc
#endif /* CONFIG_PM_RUNTIME */
+#ifdef CONFIG_PM_SLEEP
+
+/**
+ * pm_genpd_prepare - Start power transition of a device in a power 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 pwr_domain field points to the domain member of
+ * an object of type struct generic_power_domain representing a power domain
+ * consisting of I/O devices.
+ */
+static int pm_genpd_prepare(struct device *dev)
+{
+ struct generic_power_domain *genpd;
+
+ dev_dbg(dev, "%s()\n", __func__);
+
+ if (IS_ERR_OR_NULL(dev->pwr_domain))
+ return -EINVAL;
+
+ genpd = container_of(dev->pwr_domain,
+ struct generic_power_domain, domain);
+
+ return genpd->power_is_off ? 0 : pm_generic_prepare(dev);
+}
+
+/**
+ * pm_genpd_suspend - Suspend a device belonging to an I/O power domain.
+ * @dev: Device to suspend.
+ *
+ * Suspend a device under the assumption that its pwr_domain field points to the
+ * domain member of an object of type struct generic_power_domain representing
+ * a power domain consisting of I/O devices.
+ */
+static int pm_genpd_suspend(struct device *dev)
+{
+ struct generic_power_domain *genpd;
+
+ dev_dbg(dev, "%s()\n", __func__);
+
+ if (IS_ERR_OR_NULL(dev->pwr_domain))
+ return -EINVAL;
+
+ genpd = container_of(dev->pwr_domain,
+ struct generic_power_domain, domain);
+
+ if (genpd->power_is_off)
+ return 0;
+
+ /*
+ * If the device is in the (runtime) "suspended" state, call
+ * ->start_device() for it, if defined.
+ */
+ pm_runtime_resume(dev);
+
+ return pm_generic_suspend(dev);
+}
+
+/**
+ * pm_genpd_suspend_noirq - Late suspend of a device from an I/O power domain.
+ * @dev: Device to suspend.
+ *
+ * Carry out a late suspend of a device under the assumption that its
+ * pwr_domain field points to the domain member of an object of type
+ * struct generic_power_domain representing a power domain consisting of I/O
+ * devices.
+ */
+static int pm_genpd_suspend_noirq(struct device *dev)
+{
+ struct generic_power_domain *genpd;
+
+ dev_dbg(dev, "%s()\n", __func__);
+
+ if (IS_ERR_OR_NULL(dev->pwr_domain))
+ return -EINVAL;
+
+ genpd = container_of(dev->pwr_domain,
+ struct generic_power_domain, domain);
+
+ if (genpd->power_is_off)
+ return 0;
+
+ if (genpd->stop_device)
+ genpd->stop_device(dev);
+
+ mutex_lock(&genpd->lock);
+ if (++genpd->suspended_count == genpd->device_count) {
+ if (genpd->power_off)
+ genpd->power_off(&genpd->domain);
+ }
+ mutex_unlock(&genpd->lock);
+
+ 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
+ * pwr_domain field points to the domain member of an object of type
+ * struct generic_power_domain representing a power domain consisting of I/O
+ * devices.
+ */
+static int pm_genpd_resume_noirq(struct device *dev)
+{
+ struct generic_power_domain *genpd;
+
+ dev_dbg(dev, "%s()\n", __func__);
+
+ if (IS_ERR_OR_NULL(dev->pwr_domain))
+ return -EINVAL;
+
+ genpd = container_of(dev->pwr_domain,
+ struct generic_power_domain, domain);
+
+ if (genpd->power_is_off)
+ return 0;
+
+ mutex_lock(&genpd->lock);
+ if (genpd->suspended_count == genpd->device_count) {
+ if (genpd->power_on) {
+ int ret = genpd->power_on(&genpd->domain);
+ if (ret) {
+ mutex_unlock(&genpd->lock);
+ return ret;
+ }
+ }
+ }
+ genpd->suspended_count--;
+ mutex_unlock(&genpd->lock);
+
+ if (genpd->start_device)
+ genpd->start_device(dev);
+
+ return 0;
+}
+
+/**
+ * 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 pwr_domain field points to the
+ * domain member of an object of type struct generic_power_domain representing
+ * a power domain consisting of I/O devices.
+ */
+static int pm_genpd_resume(struct device *dev)
+{
+ struct generic_power_domain *genpd;
+
+ dev_dbg(dev, "%s()\n", __func__);
+
+ if (IS_ERR_OR_NULL(dev->pwr_domain))
+ return -EINVAL;
+
+ genpd = container_of(dev->pwr_domain,
+ struct generic_power_domain, domain);
+
+ return genpd->power_is_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 pwr_domain field points to the
+ * domain member of an object of type struct generic_power_domain representing
+ * a power domain consisting of I/O devices.
+ */
+static int pm_genpd_freeze(struct device *dev)
+{
+ struct generic_power_domain *genpd;
+
+ dev_dbg(dev, "%s()\n", __func__);
+
+ if (IS_ERR_OR_NULL(dev->pwr_domain))
+ return -EINVAL;
+
+ genpd = container_of(dev->pwr_domain,
+ struct generic_power_domain, domain);
+
+ if (genpd->power_is_off)
+ return 0;
+
+ /*
+ * If the device is in the (runtime) "suspended" state, call
+ * ->start_device() for it, if defined.
+ */
+ pm_runtime_resume(dev);
+
+ return 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
+ * pwr_domain field points to the domain member of an object of type
+ * struct generic_power_domain representing a power domain consisting of I/O
+ * devices.
+ */
+static int pm_genpd_freeze_noirq(struct device *dev)
+{
+ struct generic_power_domain *genpd;
+
+ dev_dbg(dev, "%s()\n", __func__);
+
+ if (IS_ERR_OR_NULL(dev->pwr_domain))
+ return -EINVAL;
+
+ genpd = container_of(dev->pwr_domain,
+ struct generic_power_domain, domain);
+
+ if (!genpd->power_is_off && 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
+ * pwr_domain field points to the domain member of an object of type
+ * struct generic_power_domain representing a power domain consisting of I/O
+ * devices.
+ */
+static int pm_genpd_thaw_noirq(struct device *dev)
+{
+ struct generic_power_domain *genpd;
+
+ dev_dbg(dev, "%s()\n", __func__);
+
+ if (IS_ERR_OR_NULL(dev->pwr_domain))
+ return -EINVAL;
+
+ genpd = container_of(dev->pwr_domain,
+ struct generic_power_domain, domain);
+
+ if (!genpd->power_is_off && genpd->start_device)
+ genpd->start_device(dev);
+
+ return 0;
+}
+
+/**
+ * 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 pwr_domain field points to the
+ * domain member of an object of type struct generic_power_domain representing
+ * a power domain consisting of I/O devices.
+ */
+static int pm_genpd_thaw(struct device *dev)
+{
+ struct generic_power_domain *genpd;
+
+ dev_dbg(dev, "%s()\n", __func__);
+
+ if (IS_ERR_OR_NULL(dev->pwr_domain))
+ return -EINVAL;
+
+ genpd = container_of(dev->pwr_domain,
+ struct generic_power_domain, domain);
+
+ return genpd->power_is_off ? 0 : pm_generic_thaw(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 pwr_domain field points to the
+ * domain member of an object of type struct generic_power_domain representing
+ * a power domain consisting of I/O devices.
+ */
+static void pm_genpd_complete(struct device *dev)
+{
+ struct generic_power_domain *genpd;
+
+ dev_dbg(dev, "%s()\n", __func__);
+
+ if (IS_ERR_OR_NULL(dev->pwr_domain))
+ return;
+
+ genpd = container_of(dev->pwr_domain,
+ struct generic_power_domain, domain);
+
+ if (!genpd->power_is_off)
+ pm_generic_complete(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_complete NULL
+
+#endif /* CONFIG_PM_SLEEP */
+
/**
* pm_genpd_add_device - Add a device to an I/O power domain.
* @genpd: Power domain to add the device to.
@@ -304,6 +614,7 @@ int pm_genpd_add_device(struct generic_p
dle->dev = dev;
list_add_tail(&dle->node, &genpd->device_list);
+ genpd->device_count++;
spin_lock_irq(&dev->power.lock);
dev->pwr_domain = &genpd->domain;
@@ -341,6 +652,7 @@ int pm_genpd_remove_device(struct generi
dev->pwr_domain = NULL;
spin_unlock_irq(&dev->power.lock);
+ genpd->device_count--;
list_del(&dle->node);
kfree(dle);
@@ -438,7 +750,23 @@ void pm_genpd_init(struct generic_power_
genpd->gov = gov;
genpd->in_progress = 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_suspend;
+ genpd->domain.ops.poweroff_noirq = pm_genpd_suspend_noirq;
+ genpd->domain.ops.restore_noirq = pm_genpd_resume_noirq;
+ genpd->domain.ops.restore = pm_genpd_resume;
+ genpd->domain.ops.complete = pm_genpd_complete;
}
Index: linux-2.6/include/linux/pm_domain.h
===================================================================
--- linux-2.6.orig/include/linux/pm_domain.h
+++ linux-2.6/include/linux/pm_domain.h
@@ -25,6 +25,8 @@ struct generic_power_domain {
struct dev_power_governor *gov;
unsigned int in_progress;
bool power_is_off;
+ unsigned int device_count;
+ unsigned int suspended_count;
int (*power_off)(struct dev_power_domain *domain);
int (*power_on)(struct dev_power_domain *domain);
int (*start_device)(struct device *dev);
On Mon, May 9, 2011 at 5:22 AM, Rafael J. Wysocki <[email protected]> wrote:
> From: Rafael J. Wysocki <[email protected]>
> +
> +/**
> + * __pm_genpd_restore_device - Restore a pre-suspend state of a device.
> + * @dev: Device to restore the state of.
> + * @genpd: Power domain the device belongs to.
> + */
> +static void __pm_genpd_restore_device(struct device *dev,
> + ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? struct generic_power_domain *genpd)
> +{
> + ? ? ? struct device_driver *drv = dev->driver;
> +
> + ? ? ? if (genpd->start_device)
> + ? ? ? ? ? ? ? genpd->start_device(dev);
> +
> + ? ? ? if (drv && drv->pm && drv->pm->runtime_resume)
> + ? ? ? ? ? ? ? drv->pm->runtime_resume(dev);
> +
> + ? ? ? if (genpd->stop_device)
> + ? ? ? ? ? ? ? genpd->stop_device(dev);
> +}
Why call ->stop_device in restore function?
BTW,
you implemented power domains for ARM / shmobile,
What can we do for other arches?
I wonder how to implement this "power domain" on x86.
Thanks,
Lin Ming
On Tuesday, May 10, 2011, Lin Ming wrote:
> On Mon, May 9, 2011 at 5:22 AM, Rafael J. Wysocki <[email protected]> wrote:
> > From: Rafael J. Wysocki <[email protected]>
>
> > +
> > +/**
> > + * __pm_genpd_restore_device - Restore a pre-suspend state of a device.
> > + * @dev: Device to restore the state of.
> > + * @genpd: Power domain the device belongs to.
> > + */
> > +static void __pm_genpd_restore_device(struct device *dev,
> > + struct generic_power_domain *genpd)
> > +{
> > + struct device_driver *drv = dev->driver;
> > +
> > + if (genpd->start_device)
> > + genpd->start_device(dev);
> > +
> > + if (drv && drv->pm && drv->pm->runtime_resume)
> > + drv->pm->runtime_resume(dev);
> > +
> > + if (genpd->stop_device)
> > + genpd->stop_device(dev);
> > +}
>
> Why call ->stop_device in restore function?
Because the device is still suspended at this point, most likely.
Presumably drv->pm->runtime_resume(dev) has restored it's registers, but
the device's runtime PM status hasn't changed (yet).
> BTW,
> you implemented power domains for ARM / shmobile,
> What can we do for other arches?
> I wonder how to implement this "power domain" on x86.
It depends on what kind of x86 you mean. On PCs we can't control power
domains directly and it is done through ACPI power resources, which works.
I don't think there's a need to rework this.
On Moorestown and similar, I can't say, since I don't know how the hardware
is designed.
Thanks,
Rafael
From: Rafael J. Wysocki <[email protected]>
Make generic power domains support system-wide power transitions
(system suspend and hibernation). Add suspend, resume, freeze and
thaw callbacks to be associated with struct generic_power_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
power domain ->suspend_noirq() callback stops the device and
eventually removes power from the power domain it belongs to (after
all devices in the power domain have been stopped). During system
resume the power domain ->resume_noirq() callback powers up the power
domain (when executed for the first time for the given power domain)
and starts the device, 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 <[email protected]>
---
Hi,
In this version of the patch I added dev_to_genpd() to reduce code
duplication slightly.
Thanks,
Rafael
---
drivers/base/power/domain.c | 330 ++++++++++++++++++++++++++++++++++++++++++--
include/linux/pm_domain.h | 2
2 files changed, 324 insertions(+), 8 deletions(-)
Index: linux-2.6/drivers/base/power/domain.c
===================================================================
--- linux-2.6.orig/drivers/base/power/domain.c
+++ linux-2.6/drivers/base/power/domain.c
@@ -14,6 +14,19 @@
#include <linux/slab.h>
#include <linux/err.h>
+#ifdef CONFIG_PM
+
+static struct generic_power_domain *dev_to_genpd(struct device *dev)
+{
+ if (IS_ERR_OR_NULL(dev->pwr_domain))
+ return ERR_PTR(-EINVAL);
+
+ return container_of(dev->pwr_domain,
+ struct generic_power_domain, domain);
+}
+
+#endif /* CONFIG_PM */
+
#ifdef CONFIG_PM_RUNTIME
/**
@@ -143,12 +156,10 @@ static int pm_genpd_runtime_suspend(stru
dev_dbg(dev, "%s()\n", __func__);
- if (IS_ERR_OR_NULL(dev->pwr_domain))
+ genpd = dev_to_genpd(dev);
+ if (IS_ERR(genpd))
return -EINVAL;
- genpd = container_of(dev->pwr_domain,
- struct generic_power_domain, domain);
-
mutex_lock(&genpd->lock);
if (genpd->stop_device) {
@@ -250,12 +261,10 @@ static int pm_genpd_runtime_resume(struc
dev_dbg(dev, "%s()\n", __func__);
- if (IS_ERR_OR_NULL(dev->pwr_domain))
+ genpd = dev_to_genpd(dev);
+ if (IS_ERR(genpd))
return -EINVAL;
- genpd = container_of(dev->pwr_domain,
- struct generic_power_domain, domain);
-
ret = pm_genpd_poweron(genpd);
if (ret)
return ret;
@@ -273,6 +282,293 @@ static int pm_genpd_runtime_resume(struc
#endif /* CONFIG_PM_RUNTIME */
+#ifdef CONFIG_PM_SLEEP
+
+/**
+ * pm_genpd_prepare - Start power transition of a device in a power 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 pwr_domain field points to the domain member of
+ * an object of type struct generic_power_domain representing a power domain
+ * consisting of I/O devices.
+ */
+static int pm_genpd_prepare(struct device *dev)
+{
+ struct generic_power_domain *genpd;
+
+ dev_dbg(dev, "%s()\n", __func__);
+
+ genpd = dev_to_genpd(dev);
+ if (IS_ERR(genpd))
+ return -EINVAL;
+
+ return genpd->power_is_off ? 0 : pm_generic_prepare(dev);
+}
+
+/**
+ * pm_genpd_suspend - Suspend a device belonging to an I/O power domain.
+ * @dev: Device to suspend.
+ *
+ * Suspend a device under the assumption that its pwr_domain field points to the
+ * domain member of an object of type struct generic_power_domain representing
+ * a power domain consisting of I/O devices.
+ */
+static int pm_genpd_suspend(struct device *dev)
+{
+ struct generic_power_domain *genpd;
+
+ dev_dbg(dev, "%s()\n", __func__);
+
+ genpd = dev_to_genpd(dev);
+ if (IS_ERR(genpd))
+ return -EINVAL;
+
+ if (genpd->power_is_off)
+ return 0;
+
+ /*
+ * If the device is in the (runtime) "suspended" state, call
+ * ->start_device() for it, if defined.
+ */
+ pm_runtime_resume(dev);
+
+ return pm_generic_suspend(dev);
+}
+
+/**
+ * pm_genpd_suspend_noirq - Late suspend of a device from an I/O power domain.
+ * @dev: Device to suspend.
+ *
+ * Carry out a late suspend of a device under the assumption that its
+ * pwr_domain field points to the domain member of an object of type
+ * struct generic_power_domain representing a power domain consisting of I/O
+ * devices.
+ */
+static int pm_genpd_suspend_noirq(struct device *dev)
+{
+ struct generic_power_domain *genpd;
+
+ dev_dbg(dev, "%s()\n", __func__);
+
+ genpd = dev_to_genpd(dev);
+ if (IS_ERR(genpd))
+ return -EINVAL;
+
+ if (genpd->power_is_off)
+ return 0;
+
+ if (genpd->stop_device)
+ genpd->stop_device(dev);
+
+ mutex_lock(&genpd->lock);
+ if (++genpd->suspended_count == genpd->device_count) {
+ if (genpd->power_off)
+ genpd->power_off(&genpd->domain);
+ }
+ mutex_unlock(&genpd->lock);
+
+ 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
+ * pwr_domain field points to the domain member of an object of type
+ * struct generic_power_domain representing a power domain consisting of I/O
+ * devices.
+ */
+static int pm_genpd_resume_noirq(struct device *dev)
+{
+ struct generic_power_domain *genpd;
+
+ dev_dbg(dev, "%s()\n", __func__);
+
+ genpd = dev_to_genpd(dev);
+ if (IS_ERR(genpd))
+ return -EINVAL;
+
+ if (genpd->power_is_off)
+ return 0;
+
+ mutex_lock(&genpd->lock);
+ if (genpd->suspended_count == genpd->device_count) {
+ if (genpd->power_on) {
+ int ret = genpd->power_on(&genpd->domain);
+ if (ret) {
+ mutex_unlock(&genpd->lock);
+ return ret;
+ }
+ }
+ }
+ genpd->suspended_count--;
+ mutex_unlock(&genpd->lock);
+
+ if (genpd->start_device)
+ genpd->start_device(dev);
+
+ return 0;
+}
+
+/**
+ * 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 pwr_domain field points to the
+ * domain member of an object of type struct generic_power_domain representing
+ * a power domain consisting of I/O devices.
+ */
+static int pm_genpd_resume(struct device *dev)
+{
+ struct generic_power_domain *genpd;
+
+ dev_dbg(dev, "%s()\n", __func__);
+
+ genpd = dev_to_genpd(dev);
+ if (IS_ERR(genpd))
+ return -EINVAL;
+
+ return genpd->power_is_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 pwr_domain field points to the
+ * domain member of an object of type struct generic_power_domain representing
+ * a power domain consisting of I/O devices.
+ */
+static int pm_genpd_freeze(struct device *dev)
+{
+ struct generic_power_domain *genpd;
+
+ dev_dbg(dev, "%s()\n", __func__);
+
+ genpd = dev_to_genpd(dev);
+ if (IS_ERR(genpd))
+ return -EINVAL;
+
+ if (genpd->power_is_off)
+ return 0;
+
+ /*
+ * If the device is in the (runtime) "suspended" state, call
+ * ->start_device() for it, if defined.
+ */
+ pm_runtime_resume(dev);
+
+ return 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
+ * pwr_domain field points to the domain member of an object of type
+ * struct generic_power_domain representing a power domain consisting of I/O
+ * devices.
+ */
+static int pm_genpd_freeze_noirq(struct device *dev)
+{
+ struct generic_power_domain *genpd;
+
+ dev_dbg(dev, "%s()\n", __func__);
+
+ genpd = dev_to_genpd(dev);
+ if (IS_ERR(genpd))
+ return -EINVAL;
+
+ if (!genpd->power_is_off && 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
+ * pwr_domain field points to the domain member of an object of type
+ * struct generic_power_domain representing a power domain consisting of I/O
+ * devices.
+ */
+static int pm_genpd_thaw_noirq(struct device *dev)
+{
+ struct generic_power_domain *genpd;
+
+ dev_dbg(dev, "%s()\n", __func__);
+
+ genpd = dev_to_genpd(dev);
+ if (IS_ERR(genpd))
+ return -EINVAL;
+
+ if (!genpd->power_is_off && genpd->start_device)
+ genpd->start_device(dev);
+
+ return 0;
+}
+
+/**
+ * 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 pwr_domain field points to the
+ * domain member of an object of type struct generic_power_domain representing
+ * a power domain consisting of I/O devices.
+ */
+static int pm_genpd_thaw(struct device *dev)
+{
+ struct generic_power_domain *genpd;
+
+ dev_dbg(dev, "%s()\n", __func__);
+
+ genpd = dev_to_genpd(dev);
+ if (IS_ERR(genpd))
+ return -EINVAL;
+
+ return genpd->power_is_off ? 0 : pm_generic_thaw(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 pwr_domain field points to the
+ * domain member of an object of type struct generic_power_domain representing
+ * a power domain consisting of I/O devices.
+ */
+static void pm_genpd_complete(struct device *dev)
+{
+ struct generic_power_domain *genpd;
+
+ dev_dbg(dev, "%s()\n", __func__);
+
+ genpd = dev_to_genpd(dev);
+ if (!IS_ERR(genpd) && !genpd->power_is_off)
+ pm_generic_complete(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_complete NULL
+
+#endif /* CONFIG_PM_SLEEP */
+
/**
* pm_genpd_add_device - Add a device to an I/O power domain.
* @genpd: Power domain to add the device to.
@@ -304,6 +600,7 @@ int pm_genpd_add_device(struct generic_p
dle->dev = dev;
list_add_tail(&dle->node, &genpd->device_list);
+ genpd->device_count++;
spin_lock_irq(&dev->power.lock);
dev->pwr_domain = &genpd->domain;
@@ -341,6 +638,7 @@ int pm_genpd_remove_device(struct generi
dev->pwr_domain = NULL;
spin_unlock_irq(&dev->power.lock);
+ genpd->device_count--;
list_del(&dle->node);
kfree(dle);
@@ -438,7 +736,23 @@ void pm_genpd_init(struct generic_power_
genpd->gov = gov;
genpd->in_progress = 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_suspend;
+ genpd->domain.ops.poweroff_noirq = pm_genpd_suspend_noirq;
+ genpd->domain.ops.restore_noirq = pm_genpd_resume_noirq;
+ genpd->domain.ops.restore = pm_genpd_resume;
+ genpd->domain.ops.complete = pm_genpd_complete;
}
Index: linux-2.6/include/linux/pm_domain.h
===================================================================
--- linux-2.6.orig/include/linux/pm_domain.h
+++ linux-2.6/include/linux/pm_domain.h
@@ -25,6 +25,8 @@ struct generic_power_domain {
struct dev_power_governor *gov;
unsigned int in_progress;
bool power_is_off;
+ unsigned int device_count;
+ unsigned int suspended_count;
int (*power_off)(struct dev_power_domain *domain);
int (*power_on)(struct dev_power_domain *domain);
int (*start_device)(struct device *dev);
Hi Rafael,
On Sat, 2011-04-30 at 02:54 +0200, Rafael J. Wysocki wrote:
> From: Rafael J. Wysocki <[email protected]>
>
> Introcude common headers, helper functions and callbacks allowing
> platforms to use simple generic power domains for runtime power
> management.
>
> Introduce struct generic_power_domain to be used for representing
> power domains that each contain a number of devices and may be
> master 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_on() is
> supposed to remove power from the entire power domain
> and ->power_off() 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.
>
> Signed-off-by: Rafael J. Wysocki <[email protected]>
> Acked-by: Greg Kroah-Hartman <[email protected]>
Thanks for proposing this. It looks like a good starting point. I
haven't had the time to experiment with it on OMAP yet due to
travel/conferences, but here's at least a few comments and questions
after a brief review.
This looks like a good start for an abstraction, but I'm not sure if can
be broadly useful without some further complications.
On many SoCs, a HW power domain has more than 2 states. On OMAP for
example, a power domain can be on, inactive, retention or off.
Therefore, the 2-state approach in this patch doesn't really map well to
hardware power domains (and I'm pretty sure OMAP is not unique here.)
It also means that the binary decision of the proposed governor doesn't
necessarily map well either (e.g., based on wake-up latency constraints,
or HW bugs, you might allow an idle device might be able to go to
retention, but not to off.)
I suppose one option would be to use "off" as defined here to handle all
the !on states, and let the platform-specific code handle the details.
However, that doesn't sound all that "generic" for a generic solution.
Another possibility would be to allow a generic power domain to have
multiple states (or levels), with a governor hook for each state.
> ---
>
> Hi,
>
> This version of the patch fixes a bug that caused pm_runtime_suspend()
> (and equivalent) to return -EBUSY erroneously when suspending the last
> active device in a power domain. It has been tested on an ARM shmobile
> system.
>
> Greg, I hope your ACK is still valid. :-)
>
> Thanks,
> Rafael
>
> ---
> drivers/base/power/Makefile | 2
> drivers/base/power/domain.c | 439 ++++++++++++++++++++++++++++++++++++++++++++
> include/linux/pm.h | 3
> include/linux/pm_domain.h | 83 ++++++++
> 4 files changed, 525 insertions(+), 2 deletions(-)
>
> Index: linux-2.6/include/linux/pm_domain.h
> ===================================================================
> --- /dev/null
> +++ linux-2.6/include/linux/pm_domain.h
> @@ -0,0 +1,83 @@
> +/*
> + * pm_domain.h - Definitions and headers related to device power domains.
> + *
> + * Copyright (C) 2011 Rafael J. Wysocki <[email protected]>, Renesas Electronics Corp.
> + *
> + * This file is released under the GPLv2.
> + */
> +
> +#ifndef _LINUX_PM_DOMAIN_H
> +#define _LINUX_PM_DOMAIN_H
> +
> +#include <linux/device.h>
> +
> +struct dev_power_governor {
> + bool (*power_down_ok)(struct dev_power_domain *domain);
> +};
> +
> +struct generic_power_domain {
> + struct dev_power_domain domain;
> + struct list_head node;
> + struct generic_power_domain *master;
> + struct list_head subdomain_list;
> + struct list_head device_list;
> + struct mutex lock;
> + struct dev_power_governor *gov;
> + unsigned int in_progress;
> + bool power_is_off;
> + int (*power_off)(struct dev_power_domain *domain);
> + int (*power_on)(struct dev_power_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;
> +};
> +
> +#ifdef CONFIG_PM_RUNTIME
> +extern int pm_genpd_runtime_suspend(struct device *dev);
> +extern int pm_genpd_runtime_resume(struct device *dev);
> +#else
> +#define pm_genpd_runtime_suspend NULL;
> +#define pm_genpd_runtime_resume NULL;
> +#endif
> +
> +#ifdef CONFIG_PM
> +extern int pm_genpd_add_device(struct generic_power_domain *genpd,
> + struct device *dev);
> +extern int pm_genpd_remove_device(struct generic_power_domain *genpd,
> + struct device *dev);
> +extern int pm_genpd_add_subdomain(struct generic_power_domain *genpd,
> + struct generic_power_domain *new_subdomain);
> +extern int pm_genpd_remove_subdomain(struct generic_power_domain *genpd,
> + struct generic_power_domain *target);
> +extern void pm_genpd_init(struct generic_power_domain *genpd,
> + struct dev_power_governor *gov, bool is_off);
> +#else
> +static inline int pm_genpd_add_device(struct generic_power_domain *genpd,
> + struct device *dev)
> +{
> + return -ENOSYS;
> +}
> +static inline int pm_genpd_remove_device(struct generic_power_domain *genpd,
> + struct device *dev)
> +{
> + return -ENOSYS;
> +}
> +static inline int pm_genpd_add_subdomain(struct generic_power_domain *genpd,
> + struct generic_power_domain *new_sd)
> +{
> + return -ENOSYS;
> +}
> +static inline int pm_genpd_remove_subdomain(struct generic_power_domain *genpd,
> + struct generic_power_domain *target)
> +{
> + return -ENOSYS;
> +}
> +static inline void pm_genpd_init(struct generic_power_domain *genpd,
> + struct dev_power_governor *gov, bool is_off) {}
> +#endif
> +
> +#endif /* _LINUX_PM_DOMAIN_H */
> Index: linux-2.6/include/linux/pm.h
> ===================================================================
> --- linux-2.6.orig/include/linux/pm.h
> +++ linux-2.6/include/linux/pm.h
> @@ -472,7 +472,8 @@ extern void update_pm_runtime_accounting
> * subsystem-level and driver-level callbacks.
> */
> struct dev_power_domain {
> - struct dev_pm_ops ops;
> + struct dev_pm_ops ops;
> + void *platform_data;
> };
>
> /*
> Index: linux-2.6/drivers/base/power/Makefile
> ===================================================================
> --- linux-2.6.orig/drivers/base/power/Makefile
> +++ linux-2.6/drivers/base/power/Makefile
> @@ -1,4 +1,4 @@
> -obj-$(CONFIG_PM) += sysfs.o generic_ops.o
> +obj-$(CONFIG_PM) += sysfs.o generic_ops.o domain.o
> obj-$(CONFIG_PM_SLEEP) += main.o wakeup.o
> obj-$(CONFIG_PM_RUNTIME) += runtime.o
> obj-$(CONFIG_PM_TRACE_RTC) += trace.o
> Index: linux-2.6/drivers/base/power/domain.c
> ===================================================================
> --- /dev/null
> +++ linux-2.6/drivers/base/power/domain.c
> @@ -0,0 +1,439 @@
> +/*
> + * drivers/base/power/domain.c - Common code related to device power domains.
> + *
> + * Copyright (C) 2011 Rafael J. Wysocki <[email protected]>, Renesas Electronics Corp.
> + *
> + * This file is released under the GPLv2.
> + */
> +
> +#include <linux/init.h>
> +#include <linux/kernel.h>
> +#include <linux/io.h>
> +#include <linux/pm_runtime.h>
> +#include <linux/pm_domain.h>
> +#include <linux/slab.h>
> +#include <linux/err.h>
> +
> +#ifdef CONFIG_PM_RUNTIME
> +
> +/**
> + * __pm_genpd_restore_device - Restore a pre-suspend state of a device.
> + * @dev: Device to restore the state of.
> + * @genpd: Power domain the device belongs to.
> + */
> +static void __pm_genpd_restore_device(struct device *dev,
> + struct generic_power_domain *genpd)
> +{
> + struct device_driver *drv = dev->driver;
> +
> + if (genpd->start_device)
> + genpd->start_device(dev);
> +
> + if (drv && drv->pm && drv->pm->runtime_resume)
> + drv->pm->runtime_resume(dev);
> +
> + if (genpd->stop_device)
> + genpd->stop_device(dev);
Why the ->stop_device() here?
Based on the changelog, I'm imagining an implementation of
->start_device() to be based on clock enable and a ->stop_device to be
based on clock disable. Assuming that, after this runtime resume path,
the driver's device will still have its clocks cut. Am I missing
something here?
Maybe I don't understand what you mean by "pre-suspend" state. In my
mind, the pre-suspend state of a device would be clocks enabled.
Similar question below...
> +}
> +
> +/**
> + * __pm_genpd_poweroff - Remove power from a given power domain.
> + * @genpd: Power 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_power_domain *genpd)
> +{
> + struct generic_power_domain *subdomain;
> + struct dev_list_entry *dle;
> + unsigned int not_suspended;
> + int ret;
> +
> + if (genpd->power_is_off)
> + return 0;
> +
> + not_suspended = 0;
> + list_for_each_entry(dle, &genpd->device_list, node)
> + if (!pm_runtime_suspended(dle->dev))
> + not_suspended++;
> +
> + if (not_suspended > genpd->in_progress)
> + return -EBUSY;
> +
> + list_for_each_entry_reverse(subdomain, &genpd->subdomain_list, node) {
> + mutex_lock(&subdomain->lock);
> + ret = __pm_genpd_poweroff(subdomain);
> + mutex_unlock(&subdomain->lock);
> + if (ret)
> + return ret;
> + }
> +
> + 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->device_list, node) {
> + struct device *dev = dle->dev;
> + struct device_driver *drv = dev->driver;
> +
> + if (genpd->start_device)
> + genpd->start_device(dev);
... is the ->start_device() needed here?
Before the driver's ->runtime_suspend() method is called, I doubt it
expects its clocks to be cut.
Of course, having the (what seem to be) extra stop/start calls here
doesn't harm anything. But if the ->[start|stop]_device() calls are
expensive for a given platform, the extra overhead might be significant
for frequent runtime PM transitions.
Kevin
Hi, Rafael,
One small question that came to mind as I was looking at this patch:
> +/**
> + * pm_genpd_powered_down - Check if power has been removed from a power domain.
> + * @genpd: Power domain to check.
> + */
> +static bool pm_genpd_powered_down(struct generic_power_domain *genpd)
> +{
> + bool ret;
> +
> + mutex_lock(&genpd->lock);
> + ret = genpd->power_is_off;
> + mutex_unlock(&genpd->lock);
> +
> + return ret;
I'm not quite sure why this function exists? The lock doesn't really
change anything, since the power state can change before or after this
check regardless. If you need the power state to be stable, it seems like
the lock needs to be taken further up the stack; otherwise simply checking
genpd->power_is_off directly would seem to be sufficient. Am I missing
something?
Thanks,
jon
On Wednesday, May 11, 2011, Jonathan Corbet wrote:
> Hi, Rafael,
>
> One small question that came to mind as I was looking at this patch:
>
> > +/**
> > + * pm_genpd_powered_down - Check if power has been removed from a power domain.
> > + * @genpd: Power domain to check.
> > + */
> > +static bool pm_genpd_powered_down(struct generic_power_domain *genpd)
> > +{
> > + bool ret;
> > +
> > + mutex_lock(&genpd->lock);
> > + ret = genpd->power_is_off;
> > + mutex_unlock(&genpd->lock);
> > +
> > + return ret;
>
> I'm not quite sure why this function exists? The lock doesn't really
> change anything, since the power state can change before or after this
> check regardless. If you need the power state to be stable, it seems like
> the lock needs to be taken further up the stack; otherwise simply checking
> genpd->power_is_off directly would seem to be sufficient. Am I missing
> something?
No, you aren't. :-)
In fact, the function doesn't exist any more in the most recent version
of the patch: https://patchwork.kernel.org/patch/775412/
Thanks,
Rafael
On Wednesday, May 11, 2011, Kevin Hilman wrote:
> Hi Rafael,
Hi Kevin,
> On Sat, 2011-04-30 at 02:54 +0200, Rafael J. Wysocki wrote:
> > From: Rafael J. Wysocki <[email protected]>
> >
> > Introcude common headers, helper functions and callbacks allowing
> > platforms to use simple generic power domains for runtime power
> > management.
> >
> > Introduce struct generic_power_domain to be used for representing
> > power domains that each contain a number of devices and may be
> > master 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_on() is
> > supposed to remove power from the entire power domain
> > and ->power_off() 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.
> >
> > Signed-off-by: Rafael J. Wysocki <[email protected]>
> > Acked-by: Greg Kroah-Hartman <[email protected]>
>
> Thanks for proposing this. It looks like a good starting point.
That's exactly the purpose of it.
> I haven't had the time to experiment with it on OMAP yet due to
> travel/conferences, but here's at least a few comments and questions
> after a brief review.
>
> This looks like a good start for an abstraction, but I'm not sure if can
> be broadly useful without some further complications.
I wouldn't expect it to match every possible use case, of course, but it's
simple enough as an initial step and something we may build upon in the
future.
> On many SoCs, a HW power domain has more than 2 states. On OMAP for
> example, a power domain can be on, inactive, retention or off.
> Therefore, the 2-state approach in this patch doesn't really map well to
> hardware power domains (and I'm pretty sure OMAP is not unique here.)
Right, so we'd need to extend the approach for OMAP somehow.
> It also means that the binary decision of the proposed governor doesn't
> necessarily map well either (e.g., based on wake-up latency constraints,
> or HW bugs, you might allow an idle device might be able to go to
> retention, but not to off.)
>
> I suppose one option would be to use "off" as defined here to handle all
> the !on states, and let the platform-specific code handle the details.
> However, that doesn't sound all that "generic" for a generic solution.
>
> Another possibility would be to allow a generic power domain to have
> multiple states (or levels), with a governor hook for each state.
That's possible. It all depends on how the various domain states affect
devices. For example, if device registers survive the inactive and retention
domain states, they may be regarded as substates of a more general "on" state.
Generally, my idea was to use a relatively simple hardware model, like the
shmobile one, and develop some general code that works with it and may be
extended in various ways, pretty much as we did with runtime PM. I think the
code in this patchset is suitable for that (even on shmobile there are
more complicated power domains that will require some additional handling).
In my opinion, if wanted to start with merging something that would match
every possible hardware configuration out there, we'd end up with a 100 patches
and 10000 lines of code, or something like this. That wouldn't be easily
mergeable, as experience shows all too well. :-)
...
> > +#ifdef CONFIG_PM_RUNTIME
> > +
> > +/**
> > + * __pm_genpd_restore_device - Restore a pre-suspend state of a device.
> > + * @dev: Device to restore the state of.
> > + * @genpd: Power domain the device belongs to.
> > + */
> > +static void __pm_genpd_restore_device(struct device *dev,
> > + struct generic_power_domain *genpd)
> > +{
> > + struct device_driver *drv = dev->driver;
> > +
> > + if (genpd->start_device)
> > + genpd->start_device(dev);
> > +
> > + if (drv && drv->pm && drv->pm->runtime_resume)
> > + drv->pm->runtime_resume(dev);
> > +
> > + if (genpd->stop_device)
> > + genpd->stop_device(dev);
>
> Why the ->stop_device() here?
>
> Based on the changelog, I'm imagining an implementation of
> ->start_device() to be based on clock enable and a ->stop_device to be
> based on clock disable. Assuming that, after this runtime resume path,
> the driver's device will still have its clocks cut. Am I missing
> something here?
Please note that this code is executed in a loop for every device in
the power domain by __pm_genpd_poweron() (including the device whose
resume triggered powering up the domain). At this point all the
devices remain in the "suspended" state from the runtime PM viewpoint.
> Maybe I don't understand what you mean by "pre-suspend" state. In my
> mind, the pre-suspend state of a device would be clocks enabled.
>
> Similar question below...
>
> > +}
> > +
> > +/**
> > + * __pm_genpd_poweroff - Remove power from a given power domain.
> > + * @genpd: Power 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_power_domain *genpd)
> > +{
> > + struct generic_power_domain *subdomain;
> > + struct dev_list_entry *dle;
> > + unsigned int not_suspended;
> > + int ret;
> > +
> > + if (genpd->power_is_off)
> > + return 0;
> > +
> > + not_suspended = 0;
> > + list_for_each_entry(dle, &genpd->device_list, node)
> > + if (!pm_runtime_suspended(dle->dev))
> > + not_suspended++;
> > +
> > + if (not_suspended > genpd->in_progress)
> > + return -EBUSY;
> > +
> > + list_for_each_entry_reverse(subdomain, &genpd->subdomain_list, node) {
> > + mutex_lock(&subdomain->lock);
> > + ret = __pm_genpd_poweroff(subdomain);
> > + mutex_unlock(&subdomain->lock);
> > + if (ret)
> > + return ret;
> > + }
> > +
> > + 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->device_list, node) {
> > + struct device *dev = dle->dev;
> > + struct device_driver *drv = dev->driver;
> > +
> > + if (genpd->start_device)
> > + genpd->start_device(dev);
>
> ... is the ->start_device() needed here?
>
> Before the driver's ->runtime_suspend() method is called, I doubt it
> expects its clocks to be cut.
In fact, it does, because all of the devices must have been put into the
runtime PM's "suspended" state before that code is executed.
> Of course, having the (what seem to be) extra stop/start calls here
> doesn't harm anything. But if the ->[start|stop]_device() calls are
> expensive for a given platform, the extra overhead might be significant
> for frequent runtime PM transitions.
If the entire domain is frequently powered up and down, then I agree it will
be expensive. However, that's kind of to be expected, isn't it? ;-)
The code could be optimized to avoid starting and stopping the device that
triggers the domain poweroff, but that would make it more complicated, which
I intentionally wanted to avoid at this stage.
Again, this code is meant as a starting point for more complicated things.
How complicated they will turn out to be in the end, I can't tell right now. :-)
Thanks,
Rafael
Hi,
This is the second update of the patchset adding support for generic I/O power
domains. Patches [1-5/6] have been posted and discussed already, although in
a slightly different form. The last patch is new. The short description of
all the patches and changes since the previous update follow.
The patches are on top of the branch at:
git://git.kernel.org/pub/scm/linux/kernel/git/rafael/suspend-2.6.git power-domains
which is going to be pushed for 2.6.40. I'd like to push patch [1/6] for
2.6.40 too, since it's only slightly related to the rest of the patchset that
depends on it. Patches [2-5/6] are regarded as 2.6.41 material. Patch [6/6]
is an RFC and it may become 2.6.41 material depending on what you think.
The entire patchset has been tested with an ARM shmobile Mackerel board.
[1/6] - Introduction of generic prepare and complete callbacks for subsystems.
This patch hasn't changed since it was posted last time.
[2/6] - Support for generic I/O power domains (runtime PM part).
There are a few changes relative to the previous version. First,
I added CONFIG_PM_GENERIC_DOMAINS to be selected by platforms
wanting to use the new code (this allows, for example, x86 to avoid
compiling it unnecessarily). Second, the loop over devices in
__pm_genpd_poweroff() doesn't increment "not_suspended" for devices
without drivers, so that they don't prevent the domain from being
powered down. Finally, the kerneldoc comment for pm_genpd_init()
has been fixed.
[3/6] - Support for generic I/O power domains (system sleep part).
pm_genpd_prepare() has been modified to execute pm_runtime_resume()
for the device is the domain's power_is_off flag is clear (the
device needs to be "active" from the runtime PM viewpoint, so that
we can save its registers before suspending the whole system).
Accordingly, pm_genpd_suspend() and pm_genpd_freeze() don't call
pm_runtime_resume() any more (they don't need to do that).
[4/6] - Implementation of generic I/O power domains support for SH7372.
[5/6] - Introduction of SH7372's A4MP power domain.
[6/6] - [RFC] Support of multiple power domain states.
This shows how I think we can handle multiple power domain states
on top of the previous patches.
Comments welcome.
Thanks,
Rafael
From: Rafael J. Wysocki <[email protected]>
Introduce generic .prepare() and .complete() power management
callbacks, currently missing, that can be used by subsystems and
power domains and export them. Provide NULL definitions of all
the generic system sleep callbacks for CONFIG_PM_SLEEP unset.
Signed-off-by: Rafael J. Wysocki <[email protected]>
---
drivers/base/power/generic_ops.c | 39 +++++++++++++++++++++++++++++++++++++++
include/linux/pm.h | 26 +++++++++++++++++++-------
2 files changed, 58 insertions(+), 7 deletions(-)
Index: linux-2.6/drivers/base/power/generic_ops.c
===================================================================
--- linux-2.6.orig/drivers/base/power/generic_ops.c
+++ linux-2.6/drivers/base/power/generic_ops.c
@@ -74,6 +74,23 @@ EXPORT_SYMBOL_GPL(pm_generic_runtime_res
#ifdef CONFIG_PM_SLEEP
/**
+ * pm_generic_prepare - Generic routine preparing a device for power transition.
+ * @dev: Device to prepare.
+ *
+ * Prepare a device for a system-wide power transition.
+ */
+int pm_generic_prepare(struct device *dev)
+{
+ struct device_driver *drv = dev->driver;
+ int ret = 0;
+
+ if (drv && drv->pm && drv->pm->prepare)
+ ret = drv->pm->prepare(dev);
+
+ return ret;
+}
+
+/**
* __pm_generic_call - Generic suspend/freeze/poweroff/thaw subsystem callback.
* @dev: Device to handle.
* @event: PM transition of the system under way.
@@ -213,16 +230,38 @@ int pm_generic_restore(struct device *de
return __pm_generic_resume(dev, PM_EVENT_RESTORE);
}
EXPORT_SYMBOL_GPL(pm_generic_restore);
+
+/**
+ * pm_generic_complete - Generic routine competing a device power transition.
+ * @dev: Device to handle.
+ *
+ * Complete a device power transition during a system-wide power transition.
+ */
+void pm_generic_complete(struct device *dev)
+{
+ struct device_driver *drv = dev->driver;
+
+ if (drv && drv->pm && drv->pm->complete)
+ drv->pm->complete(dev);
+
+ /*
+ * Let runtime PM try to suspend devices that haven't been in use before
+ * going into the system-wide sleep state we're resuming from.
+ */
+ pm_runtime_idle(dev);
+}
#endif /* CONFIG_PM_SLEEP */
struct dev_pm_ops generic_subsys_pm_ops = {
#ifdef CONFIG_PM_SLEEP
+ .prepare = pm_generic_prepare,
.suspend = pm_generic_suspend,
.resume = pm_generic_resume,
.freeze = pm_generic_freeze,
.thaw = pm_generic_thaw,
.poweroff = pm_generic_poweroff,
.restore = pm_generic_restore,
+ .complete = pm_generic_complete,
#endif
#ifdef CONFIG_PM_RUNTIME
.runtime_suspend = pm_generic_runtime_suspend,
Index: linux-2.6/include/linux/pm.h
===================================================================
--- linux-2.6.orig/include/linux/pm.h
+++ linux-2.6/include/linux/pm.h
@@ -550,6 +550,16 @@ extern void __suspend_report_result(cons
} while (0)
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(struct device *dev);
+extern int pm_generic_resume(struct device *dev);
+extern int pm_generic_freeze(struct device *dev);
+extern int pm_generic_thaw(struct device *dev);
+extern int pm_generic_restore(struct device *dev);
+extern int pm_generic_poweroff(struct device *dev);
+extern void pm_generic_complete(struct device *dev);
+
#else /* !CONFIG_PM_SLEEP */
#define device_pm_lock() do {} while (0)
@@ -566,6 +576,15 @@ static inline int device_pm_wait_for_dev
{
return 0;
}
+
+#define pm_generic_prepare NULL
+#define pm_generic_suspend NULL
+#define pm_generic_resume NULL
+#define pm_generic_freeze NULL
+#define pm_generic_thaw NULL
+#define pm_generic_restore NULL
+#define pm_generic_poweroff NULL
+#define pm_generic_complete NULL
#endif /* !CONFIG_PM_SLEEP */
/* How to reorder dpm_list after device_move() */
@@ -576,11 +595,4 @@ enum dpm_order {
DPM_ORDER_DEV_LAST,
};
-extern int pm_generic_suspend(struct device *dev);
-extern int pm_generic_resume(struct device *dev);
-extern int pm_generic_freeze(struct device *dev);
-extern int pm_generic_thaw(struct device *dev);
-extern int pm_generic_restore(struct device *dev);
-extern int pm_generic_poweroff(struct device *dev);
-
#endif /* _LINUX_PM_H */
From: Rafael J. Wysocki <[email protected]>
Introduce common headers, helper functions and callbacks allowing
platforms to use simple generic power domains for runtime power
management.
Introduce struct generic_power_domain to be used for representing
power domains that each contain a number of devices and may be
master 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 <[email protected]>
Acked-by: Greg Kroah-Hartman <[email protected]>
---
drivers/base/power/Makefile | 1
drivers/base/power/domain.c | 446 ++++++++++++++++++++++++++++++++++++++++++++
include/linux/pm.h | 3
include/linux/pm_domain.h | 75 +++++++
kernel/power/Kconfig | 4
5 files changed, 528 insertions(+), 1 deletion(-)
Index: linux-2.6/include/linux/pm_domain.h
===================================================================
--- /dev/null
+++ linux-2.6/include/linux/pm_domain.h
@@ -0,0 +1,75 @@
+/*
+ * pm_domain.h - Definitions and headers related to device power domains.
+ *
+ * Copyright (C) 2011 Rafael J. Wysocki <[email protected]>, Renesas Electronics Corp.
+ *
+ * This file is released under the GPLv2.
+ */
+
+#ifndef _LINUX_PM_DOMAIN_H
+#define _LINUX_PM_DOMAIN_H
+
+#include <linux/device.h>
+
+struct dev_power_governor {
+ bool (*power_down_ok)(struct dev_power_domain *domain);
+};
+
+struct generic_power_domain {
+ struct dev_power_domain domain;
+ struct list_head node;
+ struct generic_power_domain *master;
+ struct list_head subdomain_list;
+ struct list_head device_list;
+ struct mutex lock;
+ struct dev_power_governor *gov;
+ unsigned int in_progress;
+ bool power_is_off;
+ int (*power_off)(struct dev_power_domain *domain);
+ int (*power_on)(struct dev_power_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;
+};
+
+#ifdef CONFIG_PM_GENERIC_DOMAINS
+extern int pm_genpd_add_device(struct generic_power_domain *genpd,
+ struct device *dev);
+extern int pm_genpd_remove_device(struct generic_power_domain *genpd,
+ struct device *dev);
+extern int pm_genpd_add_subdomain(struct generic_power_domain *genpd,
+ struct generic_power_domain *new_subdomain);
+extern int pm_genpd_remove_subdomain(struct generic_power_domain *genpd,
+ struct generic_power_domain *target);
+extern void pm_genpd_init(struct generic_power_domain *genpd,
+ struct dev_power_governor *gov, bool is_off);
+#else
+static inline int pm_genpd_add_device(struct generic_power_domain *genpd,
+ struct device *dev)
+{
+ return -ENOSYS;
+}
+static inline int pm_genpd_remove_device(struct generic_power_domain *genpd,
+ struct device *dev)
+{
+ return -ENOSYS;
+}
+static inline int pm_genpd_add_subdomain(struct generic_power_domain *genpd,
+ struct generic_power_domain *new_sd)
+{
+ return -ENOSYS;
+}
+static inline int pm_genpd_remove_subdomain(struct generic_power_domain *genpd,
+ struct generic_power_domain *target)
+{
+ return -ENOSYS;
+}
+static inline void pm_genpd_init(struct generic_power_domain *genpd,
+ struct dev_power_governor *gov, bool is_off) {}
+#endif
+
+#endif /* _LINUX_PM_DOMAIN_H */
Index: linux-2.6/include/linux/pm.h
===================================================================
--- linux-2.6.orig/include/linux/pm.h
+++ linux-2.6/include/linux/pm.h
@@ -472,7 +472,8 @@ extern void update_pm_runtime_accounting
* subsystem-level and driver-level callbacks.
*/
struct dev_power_domain {
- struct dev_pm_ops ops;
+ struct dev_pm_ops ops;
+ void *platform_data;
};
/*
Index: linux-2.6/drivers/base/power/Makefile
===================================================================
--- linux-2.6.orig/drivers/base/power/Makefile
+++ linux-2.6/drivers/base/power/Makefile
@@ -3,6 +3,7 @@ obj-$(CONFIG_PM_SLEEP) += main.o wakeup.
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
Index: linux-2.6/drivers/base/power/domain.c
===================================================================
--- /dev/null
+++ linux-2.6/drivers/base/power/domain.c
@@ -0,0 +1,446 @@
+/*
+ * drivers/base/power/domain.c - Common code related to device power domains.
+ *
+ * Copyright (C) 2011 Rafael J. Wysocki <[email protected]>, Renesas Electronics Corp.
+ *
+ * This file is released under the GPLv2.
+ */
+
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/io.h>
+#include <linux/pm_runtime.h>
+#include <linux/pm_domain.h>
+#include <linux/slab.h>
+#include <linux/err.h>
+
+#ifdef CONFIG_PM_RUNTIME
+
+/**
+ * __pm_genpd_restore_device - Restore a pre-suspend state of a device.
+ * @dev: Device to restore the state of.
+ * @genpd: Power domain the device belongs to.
+ */
+static void __pm_genpd_restore_device(struct device *dev,
+ struct generic_power_domain *genpd)
+{
+ struct device_driver *drv = dev->driver;
+
+ if (genpd->start_device)
+ genpd->start_device(dev);
+
+ if (drv && drv->pm && drv->pm->runtime_resume)
+ drv->pm->runtime_resume(dev);
+
+ if (genpd->stop_device)
+ genpd->stop_device(dev);
+}
+
+/**
+ * __pm_genpd_poweroff - Remove power from a given power domain.
+ * @genpd: Power 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_power_domain *genpd)
+{
+ struct generic_power_domain *subdomain;
+ struct dev_list_entry *dle;
+ unsigned int not_suspended;
+ int ret;
+
+ if (genpd->power_is_off)
+ return 0;
+
+ not_suspended = 0;
+ list_for_each_entry(dle, &genpd->device_list, node)
+ if (dle->dev->driver && !pm_runtime_suspended(dle->dev))
+ not_suspended++;
+
+ if (not_suspended > genpd->in_progress)
+ return -EBUSY;
+
+ list_for_each_entry_reverse(subdomain, &genpd->subdomain_list, node) {
+ mutex_lock(&subdomain->lock);
+ ret = __pm_genpd_poweroff(subdomain);
+ mutex_unlock(&subdomain->lock);
+ if (ret)
+ return ret;
+ }
+
+ 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->device_list, node) {
+ struct device *dev = dle->dev;
+ struct device_driver *drv = dev->driver;
+
+ if (genpd->start_device)
+ genpd->start_device(dev);
+
+ if (drv && drv->pm && drv->pm->runtime_suspend)
+ ret = drv->pm->runtime_suspend(dev);
+
+ if (genpd->stop_device)
+ genpd->stop_device(dev);
+
+ if (ret)
+ goto err_dev;
+ }
+
+ if (genpd->power_off)
+ genpd->power_off(&genpd->domain);
+
+ genpd->power_is_off = true;
+
+ return 0;
+
+ err_dev:
+ list_for_each_entry_continue(dle, &genpd->device_list, node)
+ __pm_genpd_restore_device(dle->dev, genpd);
+
+ return ret;
+}
+
+/**
+ * pm_genpd_poweroff - Remove power from a given power domain and its masters.
+ * @genpd: Power domain to power down.
+ *
+ * Try to remove power from @genpd and all of its masters in order to save as
+ * much power as possible.
+ */
+static void pm_genpd_poweroff(struct generic_power_domain *genpd)
+{
+ struct generic_power_domain *master;
+
+ mutex_lock(&genpd->lock);
+ master = genpd->master;
+ if (master) {
+ mutex_unlock(&genpd->lock);
+ pm_genpd_poweroff(master);
+ return;
+ }
+ __pm_genpd_poweroff(genpd);
+ mutex_unlock(&genpd->lock);
+}
+
+/**
+ * pm_genpd_runtime_suspend - Suspend a device belonging to I/O power domain.
+ * @dev: Device to suspend.
+ *
+ * Carry out a runtime suspend of a device under the assumption that its
+ * pwr_domain field points to the domain member of an object of type
+ * struct generic_power_domain representing a power domain consisting of I/O
+ * devices.
+ */
+static int pm_genpd_runtime_suspend(struct device *dev)
+{
+ struct generic_power_domain *genpd, *master;
+
+ dev_dbg(dev, "%s()\n", __func__);
+
+ if (IS_ERR_OR_NULL(dev->pwr_domain))
+ return -EINVAL;
+
+ genpd = container_of(dev->pwr_domain,
+ struct generic_power_domain, domain);
+
+ mutex_lock(&genpd->lock);
+
+ if (genpd->stop_device) {
+ int ret = genpd->stop_device(dev);
+ if (ret)
+ goto out;
+ }
+ genpd->in_progress++;
+
+ master = genpd->master;
+ if (master) {
+ mutex_unlock(&genpd->lock);
+
+ pm_genpd_poweroff(master);
+
+ mutex_lock(&genpd->lock);
+ } else {
+ __pm_genpd_poweroff(genpd);
+ }
+
+ genpd->in_progress--;
+
+ out:
+ mutex_unlock(&genpd->lock);
+
+ return 0;
+}
+
+/**
+ * __pm_genpd_poweron - Restore power for a given power domain.
+ * @genpd: Power domain to power up.
+ *
+ * Restore power for @genpd and run runtime resume callbacks provided by all of
+ * its devices' drivers.
+ */
+static int __pm_genpd_poweron(struct generic_power_domain *genpd)
+{
+ struct dev_list_entry *dle;
+
+ if (!genpd->power_is_off)
+ return 0;
+
+ if (genpd->power_on) {
+ int ret = genpd->power_on(&genpd->domain);
+ if (ret)
+ return ret;
+ }
+
+ genpd->power_is_off = false;
+
+ list_for_each_entry(dle, &genpd->device_list, node)
+ __pm_genpd_restore_device(dle->dev, genpd);
+
+ return 0;
+}
+
+/**
+ * pm_genpd_poweron - Restore power for a given power domain and its masters.
+ * @genpd: Power domain to power up.
+ *
+ * Restore power for @genpd and all of its masters so that it is possible to
+ * resume a device belonging to it.
+ */
+static int pm_genpd_poweron(struct generic_power_domain *genpd)
+{
+ struct generic_power_domain *master;
+ int ret;
+
+ mutex_lock(&genpd->lock);
+ master = genpd->master;
+ if (master) {
+ mutex_unlock(&genpd->lock);
+
+ ret = pm_genpd_poweron(master);
+ if (ret)
+ return ret;
+
+ mutex_lock(&genpd->lock);
+ }
+ ret = __pm_genpd_poweron(genpd);
+ mutex_unlock(&genpd->lock);
+
+ return ret;
+}
+
+/**
+ * pm_genpd_runtime_resume - Resume a device belonging to I/O power domain.
+ * @dev: Device to resume.
+ *
+ * Carry out a runtime resume of a device under the assumption that its
+ * pwr_domain field points to the domain member of an object of type
+ * struct generic_power_domain representing a power domain consisting of I/O
+ * devices.
+ */
+static int pm_genpd_runtime_resume(struct device *dev)
+{
+ struct generic_power_domain *genpd;
+ int ret;
+
+ dev_dbg(dev, "%s()\n", __func__);
+
+ if (IS_ERR_OR_NULL(dev->pwr_domain))
+ return -EINVAL;
+
+ genpd = container_of(dev->pwr_domain,
+ struct generic_power_domain, domain);
+
+ ret = pm_genpd_poweron(genpd);
+ if (ret)
+ return ret;
+
+ if (genpd->start_device)
+ genpd->start_device(dev);
+
+ return 0;
+}
+
+#else
+
+#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 power domain.
+ * @genpd: Power domain to add the device to.
+ * @dev: Device to be added.
+ */
+int pm_genpd_add_device(struct generic_power_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);
+
+ list_for_each_entry(dle, &genpd->device_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;
+ list_add_tail(&dle->node, &genpd->device_list);
+
+ spin_lock_irq(&dev->power.lock);
+ dev->pwr_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 power domain.
+ * @genpd: Power domain to remove the device from.
+ * @dev: Device to be removed.
+ */
+int pm_genpd_remove_device(struct generic_power_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->device_list, node) {
+ if (dle->dev != dev)
+ continue;
+
+ spin_lock_irq(&dev->power.lock);
+ dev->pwr_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 power domain.
+ * @genpd: Master power domain to add the subdomain to.
+ * @new_subdomain: Subdomain to be added.
+ */
+int pm_genpd_add_subdomain(struct generic_power_domain *genpd,
+ struct generic_power_domain *new_subdomain)
+{
+ struct generic_power_domain *subdomain;
+ int ret = 0;
+
+ if (IS_ERR_OR_NULL(genpd) || IS_ERR_OR_NULL(new_subdomain))
+ return -EINVAL;
+
+ mutex_lock(&genpd->lock);
+
+ list_for_each_entry(subdomain, &genpd->subdomain_list, node)
+ if (subdomain == new_subdomain) {
+ ret = -EINVAL;
+ goto out;
+ }
+
+ mutex_lock(&new_subdomain->lock);
+ list_add_tail(&new_subdomain->node, &genpd->subdomain_list);
+ new_subdomain->master = genpd;
+ mutex_unlock(&new_subdomain->lock);
+
+ out:
+ mutex_unlock(&genpd->lock);
+
+ return ret;
+}
+
+/**
+ * pm_genpd_remove_subdomain - Remove a subdomain from an I/O power domain.
+ * @genpd: Master power domain to remove the subdomain from.
+ * @target: Subdomain to be removed.
+ */
+int pm_genpd_remove_subdomain(struct generic_power_domain *genpd,
+ struct generic_power_domain *target)
+{
+ struct generic_power_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->subdomain_list, node) {
+ if (subdomain != target)
+ continue;
+
+ mutex_lock(&subdomain->lock);
+ list_del(&subdomain->node);
+ subdomain->master = NULL;
+ mutex_unlock(&subdomain->lock);
+
+ ret = 0;
+ break;
+ }
+
+ mutex_unlock(&genpd->lock);
+
+ return ret;
+}
+
+/**
+ * pm_genpd_init - Initialize a generic I/O power domain object.
+ * @genpd: Power domain object to initialize.
+ * @gov: Power 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_power_domain *genpd,
+ struct dev_power_governor *gov, bool is_off)
+{
+ if (IS_ERR_OR_NULL(genpd))
+ return;
+
+ INIT_LIST_HEAD(&genpd->node);
+ genpd->master = NULL;
+ INIT_LIST_HEAD(&genpd->device_list);
+ INIT_LIST_HEAD(&genpd->subdomain_list);
+ mutex_init(&genpd->lock);
+ genpd->gov = gov;
+ genpd->in_progress = 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;
+}
Index: linux-2.6/kernel/power/Kconfig
===================================================================
--- linux-2.6.orig/kernel/power/Kconfig
+++ linux-2.6/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
From: Rafael J. Wysocki <[email protected]>
Make generic power domains support system-wide power transitions
(system suspend and hibernation). Add suspend, resume, freeze and
thaw callbacks to be associated with struct generic_power_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
power domain ->suspend_noirq() callback stops the device and
eventually removes power from the power domain it belongs to (after
all devices in the power domain have been stopped). During system
resume the power domain ->resume_noirq() callback powers up the power
domain (when executed for the first time for the given power domain)
and starts the device, 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 <[email protected]>
---
drivers/base/power/domain.c | 321 ++++++++++++++++++++++++++++++++++++++++++--
include/linux/pm_domain.h | 2
2 files changed, 315 insertions(+), 8 deletions(-)
Index: linux-2.6/drivers/base/power/domain.c
===================================================================
--- linux-2.6.orig/drivers/base/power/domain.c
+++ linux-2.6/drivers/base/power/domain.c
@@ -14,6 +14,19 @@
#include <linux/slab.h>
#include <linux/err.h>
+#ifdef CONFIG_PM
+
+static struct generic_power_domain *dev_to_genpd(struct device *dev)
+{
+ if (IS_ERR_OR_NULL(dev->pwr_domain))
+ return ERR_PTR(-EINVAL);
+
+ return container_of(dev->pwr_domain,
+ struct generic_power_domain, domain);
+}
+
+#endif /* CONFIG_PM */
+
#ifdef CONFIG_PM_RUNTIME
/**
@@ -143,12 +156,10 @@ static int pm_genpd_runtime_suspend(stru
dev_dbg(dev, "%s()\n", __func__);
- if (IS_ERR_OR_NULL(dev->pwr_domain))
+ genpd = dev_to_genpd(dev);
+ if (IS_ERR(genpd))
return -EINVAL;
- genpd = container_of(dev->pwr_domain,
- struct generic_power_domain, domain);
-
mutex_lock(&genpd->lock);
if (genpd->stop_device) {
@@ -250,12 +261,10 @@ static int pm_genpd_runtime_resume(struc
dev_dbg(dev, "%s()\n", __func__);
- if (IS_ERR_OR_NULL(dev->pwr_domain))
+ genpd = dev_to_genpd(dev);
+ if (IS_ERR(genpd))
return -EINVAL;
- genpd = container_of(dev->pwr_domain,
- struct generic_power_domain, domain);
-
ret = pm_genpd_poweron(genpd);
if (ret)
return ret;
@@ -273,6 +282,284 @@ static int pm_genpd_runtime_resume(struc
#endif /* CONFIG_PM_RUNTIME */
+#ifdef CONFIG_PM_SLEEP
+
+/**
+ * pm_genpd_prepare - Start power transition of a device in a power 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 pwr_domain field points to the domain member of
+ * an object of type struct generic_power_domain representing a power domain
+ * consisting of I/O devices.
+ */
+static int pm_genpd_prepare(struct device *dev)
+{
+ struct generic_power_domain *genpd;
+
+ dev_dbg(dev, "%s()\n", __func__);
+
+ genpd = dev_to_genpd(dev);
+ if (IS_ERR(genpd))
+ return -EINVAL;
+
+ if (genpd->power_is_off)
+ return 0;
+
+ /*
+ * If the device is in the (runtime) "suspended" state, call
+ * ->start_device() for it, if defined.
+ */
+ pm_runtime_resume(dev);
+
+ return pm_generic_prepare(dev);
+}
+
+/**
+ * pm_genpd_suspend - Suspend a device belonging to an I/O power domain.
+ * @dev: Device to suspend.
+ *
+ * Suspend a device under the assumption that its pwr_domain field points to the
+ * domain member of an object of type struct generic_power_domain representing
+ * a power domain consisting of I/O devices.
+ */
+static int pm_genpd_suspend(struct device *dev)
+{
+ struct generic_power_domain *genpd;
+
+ dev_dbg(dev, "%s()\n", __func__);
+
+ genpd = dev_to_genpd(dev);
+ if (IS_ERR(genpd))
+ return -EINVAL;
+
+ return genpd->power_is_off ? 0 : pm_generic_suspend(dev);
+}
+
+/**
+ * pm_genpd_suspend_noirq - Late suspend of a device from an I/O power domain.
+ * @dev: Device to suspend.
+ *
+ * Carry out a late suspend of a device under the assumption that its
+ * pwr_domain field points to the domain member of an object of type
+ * struct generic_power_domain representing a power domain consisting of I/O
+ * devices.
+ */
+static int pm_genpd_suspend_noirq(struct device *dev)
+{
+ struct generic_power_domain *genpd;
+
+ dev_dbg(dev, "%s()\n", __func__);
+
+ genpd = dev_to_genpd(dev);
+ if (IS_ERR(genpd))
+ return -EINVAL;
+
+ if (genpd->power_is_off)
+ return 0;
+
+ if (genpd->stop_device)
+ genpd->stop_device(dev);
+
+ mutex_lock(&genpd->lock);
+ if (++genpd->suspended_count == genpd->device_count) {
+ if (genpd->power_off)
+ genpd->power_off(&genpd->domain);
+ }
+ mutex_unlock(&genpd->lock);
+
+ 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
+ * pwr_domain field points to the domain member of an object of type
+ * struct generic_power_domain representing a power domain consisting of I/O
+ * devices.
+ */
+static int pm_genpd_resume_noirq(struct device *dev)
+{
+ struct generic_power_domain *genpd;
+
+ dev_dbg(dev, "%s()\n", __func__);
+
+ genpd = dev_to_genpd(dev);
+ if (IS_ERR(genpd))
+ return -EINVAL;
+
+ if (genpd->power_is_off)
+ return 0;
+
+ mutex_lock(&genpd->lock);
+ if (genpd->suspended_count == genpd->device_count) {
+ if (genpd->power_on) {
+ int ret = genpd->power_on(&genpd->domain);
+ if (ret) {
+ mutex_unlock(&genpd->lock);
+ return ret;
+ }
+ }
+ }
+ genpd->suspended_count--;
+ mutex_unlock(&genpd->lock);
+
+ if (genpd->start_device)
+ genpd->start_device(dev);
+
+ return 0;
+}
+
+/**
+ * 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 pwr_domain field points to the
+ * domain member of an object of type struct generic_power_domain representing
+ * a power domain consisting of I/O devices.
+ */
+static int pm_genpd_resume(struct device *dev)
+{
+ struct generic_power_domain *genpd;
+
+ dev_dbg(dev, "%s()\n", __func__);
+
+ genpd = dev_to_genpd(dev);
+ if (IS_ERR(genpd))
+ return -EINVAL;
+
+ return genpd->power_is_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 pwr_domain field points to the
+ * domain member of an object of type struct generic_power_domain representing
+ * a power domain consisting of I/O devices.
+ */
+static int pm_genpd_freeze(struct device *dev)
+{
+ struct generic_power_domain *genpd;
+
+ dev_dbg(dev, "%s()\n", __func__);
+
+ genpd = dev_to_genpd(dev);
+ if (IS_ERR(genpd))
+ return -EINVAL;
+
+ return genpd->power_is_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
+ * pwr_domain field points to the domain member of an object of type
+ * struct generic_power_domain representing a power domain consisting of I/O
+ * devices.
+ */
+static int pm_genpd_freeze_noirq(struct device *dev)
+{
+ struct generic_power_domain *genpd;
+
+ dev_dbg(dev, "%s()\n", __func__);
+
+ genpd = dev_to_genpd(dev);
+ if (IS_ERR(genpd))
+ return -EINVAL;
+
+ if (!genpd->power_is_off && 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
+ * pwr_domain field points to the domain member of an object of type
+ * struct generic_power_domain representing a power domain consisting of I/O
+ * devices.
+ */
+static int pm_genpd_thaw_noirq(struct device *dev)
+{
+ struct generic_power_domain *genpd;
+
+ dev_dbg(dev, "%s()\n", __func__);
+
+ genpd = dev_to_genpd(dev);
+ if (IS_ERR(genpd))
+ return -EINVAL;
+
+ if (!genpd->power_is_off && genpd->start_device)
+ genpd->start_device(dev);
+
+ return 0;
+}
+
+/**
+ * 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 pwr_domain field points to the
+ * domain member of an object of type struct generic_power_domain representing
+ * a power domain consisting of I/O devices.
+ */
+static int pm_genpd_thaw(struct device *dev)
+{
+ struct generic_power_domain *genpd;
+
+ dev_dbg(dev, "%s()\n", __func__);
+
+ genpd = dev_to_genpd(dev);
+ if (IS_ERR(genpd))
+ return -EINVAL;
+
+ return genpd->power_is_off ? 0 : pm_generic_thaw(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 pwr_domain field points to the
+ * domain member of an object of type struct generic_power_domain representing
+ * a power domain consisting of I/O devices.
+ */
+static void pm_genpd_complete(struct device *dev)
+{
+ struct generic_power_domain *genpd;
+
+ dev_dbg(dev, "%s()\n", __func__);
+
+ genpd = dev_to_genpd(dev);
+ if (!IS_ERR(genpd) && !genpd->power_is_off)
+ pm_generic_complete(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_complete NULL
+
+#endif /* CONFIG_PM_SLEEP */
+
/**
* pm_genpd_add_device - Add a device to an I/O power domain.
* @genpd: Power domain to add the device to.
@@ -304,6 +591,7 @@ int pm_genpd_add_device(struct generic_p
dle->dev = dev;
list_add_tail(&dle->node, &genpd->device_list);
+ genpd->device_count++;
spin_lock_irq(&dev->power.lock);
dev->pwr_domain = &genpd->domain;
@@ -341,6 +629,7 @@ int pm_genpd_remove_device(struct generi
dev->pwr_domain = NULL;
spin_unlock_irq(&dev->power.lock);
+ genpd->device_count--;
list_del(&dle->node);
kfree(dle);
@@ -440,7 +729,23 @@ void pm_genpd_init(struct generic_power_
genpd->gov = gov;
genpd->in_progress = 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_suspend;
+ genpd->domain.ops.poweroff_noirq = pm_genpd_suspend_noirq;
+ genpd->domain.ops.restore_noirq = pm_genpd_resume_noirq;
+ genpd->domain.ops.restore = pm_genpd_resume;
+ genpd->domain.ops.complete = pm_genpd_complete;
}
Index: linux-2.6/include/linux/pm_domain.h
===================================================================
--- linux-2.6.orig/include/linux/pm_domain.h
+++ linux-2.6/include/linux/pm_domain.h
@@ -25,6 +25,8 @@ struct generic_power_domain {
struct dev_power_governor *gov;
unsigned int in_progress;
bool power_is_off;
+ unsigned int device_count;
+ unsigned int suspended_count;
int (*power_off)(struct dev_power_domain *domain);
int (*power_on)(struct dev_power_domain *domain);
int (*start_device)(struct device *dev);
From: Rafael J. Wysocki <[email protected]>
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 <[email protected]>
---
arch/arm/mach-shmobile/Kconfig | 1
arch/arm/mach-shmobile/Makefile | 4 +
arch/arm/mach-shmobile/board-mackerel.c | 3
arch/arm/mach-shmobile/include/mach/sh7372.h | 16 ++++
arch/arm/mach-shmobile/pm-sh7372.c | 97 +++++++++++++++++++++++++++
5 files changed, 121 insertions(+)
Index: linux-2.6/arch/arm/mach-shmobile/board-mackerel.c
===================================================================
--- linux-2.6.orig/arch/arm/mach-shmobile/board-mackerel.c
+++ linux-2.6/arch/arm/mach-shmobile/board-mackerel.c
@@ -1215,6 +1215,9 @@ static void __init mackerel_init(void)
platform_add_devices(mackerel_devices, ARRAY_SIZE(mackerel_devices));
+ sh7372_add_device_to_domain(SH7372_A4LC, &lcdc_device);
+ sh7372_add_device_to_domain(SH7372_A4LC, &hdmi_lcdc_device);
+
hdmi_init_pm_clock();
}
Index: linux-2.6/arch/arm/mach-shmobile/pm-sh7372.c
===================================================================
--- /dev/null
+++ linux-2.6/arch/arm/mach-shmobile/pm-sh7372.c
@@ -0,0 +1,97 @@
+/*
+ * arch/arm/mach-shmobile/pm-sh7372.c
+ *
+ * Power domains support code for SH7372
+ *
+ * Copyright (C) 2011 Rafael J. Wysocki <[email protected]>, Renesas Electronics Corp.
+ *
+ * This file is subject to the terms and conditions of the GNU General Public
+ * License v2. See the file "COPYING" in the main directory of this archive
+ * for more details.
+ */
+
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/io.h>
+#include <linux/pm_runtime.h>
+#include <linux/platform_device.h>
+
+#include <mach/sh7372.h>
+
+struct sh7372_domain_data {
+ unsigned int bit_shift;
+};
+
+#define SPDCR 0xe6180008
+#define SWUCR 0xe6180014
+#define PSTR 0xe6180080
+
+static int pd_power_down(struct dev_power_domain *domain)
+{
+ struct sh7372_domain_data *dd = domain->platform_data;
+ unsigned int mask = 1 << dd->bit_shift;
+
+ if (__raw_readl(PSTR) & mask) {
+ __raw_writel(mask, SPDCR);
+
+ while (__raw_readl(SPDCR) & mask) {}
+
+ pr_debug("sh7372 power domain down 0x%08x -> PSTR = 0x%08x\n",
+ mask, __raw_readl(PSTR));
+ }
+
+ return 0;
+}
+
+static int pd_power_up(struct dev_power_domain *domain)
+{
+ struct sh7372_domain_data *dd = domain->platform_data;
+ unsigned int mask = 1 << dd->bit_shift;
+
+ if (!(__raw_readl(PSTR) & mask)) {
+ __raw_writel(mask, SWUCR);
+
+ while (__raw_readl(SWUCR) & mask) {}
+
+ pr_debug("sh7372 power domain up 0x%08x -> PSTR = 0x%08x\n",
+ mask, __raw_readl(PSTR));
+ }
+
+ return 0;
+}
+
+static void sh7372_init_domain(struct generic_power_domain *domain,
+ struct sh7372_domain_data *pdata)
+{
+ pm_genpd_init(domain, NULL, false);
+ domain->domain.platform_data = pdata;
+ domain->stop_device = pm_runtime_clk_suspend;
+ domain->start_device = pm_runtime_clk_resume;
+ domain->power_off = pd_power_down;
+ domain->power_on = pd_power_up;
+}
+
+void sh7372_add_device_to_domain(struct generic_power_domain *domain,
+ struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+
+ if (!dev->power.subsys_data) {
+ pm_runtime_clk_init(dev);
+ pm_runtime_clk_add(dev, NULL);
+ }
+ pm_genpd_add_device(domain, dev);
+}
+
+static struct sh7372_domain_data sh7372_a4lc_domain_data = {
+ .bit_shift = 1,
+};
+
+struct generic_power_domain sh7372_a4lc_domain;
+
+static int __init sh7372_power_domains_init(void)
+{
+ sh7372_init_domain(&sh7372_a4lc_domain, &sh7372_a4lc_domain_data);
+ return 0;
+}
+core_initcall(sh7372_power_domains_init);
Index: linux-2.6/arch/arm/mach-shmobile/include/mach/sh7372.h
===================================================================
--- linux-2.6.orig/arch/arm/mach-shmobile/include/mach/sh7372.h
+++ linux-2.6/arch/arm/mach-shmobile/include/mach/sh7372.h
@@ -12,6 +12,7 @@
#define __ASM_SH7372_H__
#include <linux/sh_clk.h>
+#include <linux/pm_domain.h>
/*
* Pin Function Controller:
@@ -469,4 +470,19 @@ extern struct clk sh7372_fsibck_clk;
extern struct clk sh7372_fsidiva_clk;
extern struct clk sh7372_fsidivb_clk;
+struct platform_device;
+
+#ifdef CONFIG_PM
+extern struct generic_power_domain sh7372_a4lc_domain;
+#define SH7372_A4LC (&sh7372_a4lc_domain)
+
+extern void sh7372_add_device_to_domain(struct generic_power_domain *domain,
+ struct platform_device *pdev);
+#else
+#define SH7372_A4LC NULL
+
+static inline void sh7372_add_device_to_domain(struct generic_power_domain *dom,
+ struct platform_device *pd) {}
+#endif /* CONFIG_PM */
+
#endif /* __ASM_SH7372_H__ */
Index: linux-2.6/arch/arm/mach-shmobile/Makefile
===================================================================
--- linux-2.6.orig/arch/arm/mach-shmobile/Makefile
+++ linux-2.6/arch/arm/mach-shmobile/Makefile
@@ -40,6 +40,10 @@ obj-$(CONFIG_MACH_AP4EVB) += board-ap4ev
obj-$(CONFIG_MACH_AG5EVM) += board-ag5evm.o
obj-$(CONFIG_MACH_MACKEREL) += board-mackerel.o
+# PM objects
+pm-$(CONFIG_ARCH_SH7372) += pm-sh7372.o
+
# Framework support
obj-$(CONFIG_SMP) += $(smp-y)
obj-$(CONFIG_GENERIC_GPIO) += $(pfc-y)
+obj-$(CONFIG_PM) += $(pm-y)
Index: linux-2.6/arch/arm/mach-shmobile/Kconfig
===================================================================
--- linux-2.6.orig/arch/arm/mach-shmobile/Kconfig
+++ linux-2.6/arch/arm/mach-shmobile/Kconfig
@@ -19,6 +19,7 @@ config ARCH_SH7372
select CPU_V7
select SH_CLK_CPG
select ARCH_WANT_OPTIONAL_GPIOLIB
+ select PM_GENERIC_DOMAINS
config ARCH_SH73A0
bool "SH-Mobile AG5 (R8A73A00)"
From: Rafael J. Wysocki <[email protected]>
Use the power domains support code added by the previous
patches to implement support for power domain A4MP on SH7372.
Signed-off-by: Rafael J. Wysocki <[email protected]>
---
arch/arm/mach-shmobile/board-mackerel.c | 2 ++
arch/arm/mach-shmobile/include/mach/sh7372.h | 3 +++
arch/arm/mach-shmobile/pm-sh7372.c | 7 +++++++
3 files changed, 12 insertions(+)
Index: linux-2.6/arch/arm/mach-shmobile/pm-sh7372.c
===================================================================
--- linux-2.6.orig/arch/arm/mach-shmobile/pm-sh7372.c
+++ linux-2.6/arch/arm/mach-shmobile/pm-sh7372.c
@@ -89,9 +89,16 @@ static struct sh7372_domain_data sh7372_
struct generic_power_domain sh7372_a4lc_domain;
+static struct sh7372_domain_data sh7372_a4mp_domain_data = {
+ .bit_shift = 2,
+};
+
+struct generic_power_domain sh7372_a4mp_domain;
+
static int __init sh7372_power_domains_init(void)
{
sh7372_init_domain(&sh7372_a4lc_domain, &sh7372_a4lc_domain_data);
+ sh7372_init_domain(&sh7372_a4mp_domain, &sh7372_a4mp_domain_data);
return 0;
}
core_initcall(sh7372_power_domains_init);
Index: linux-2.6/arch/arm/mach-shmobile/include/mach/sh7372.h
===================================================================
--- linux-2.6.orig/arch/arm/mach-shmobile/include/mach/sh7372.h
+++ linux-2.6/arch/arm/mach-shmobile/include/mach/sh7372.h
@@ -475,11 +475,14 @@ struct platform_device;
#ifdef CONFIG_PM
extern struct generic_power_domain sh7372_a4lc_domain;
#define SH7372_A4LC (&sh7372_a4lc_domain)
+extern struct generic_power_domain sh7372_a4mp_domain;
+#define SH7372_A4MP (&sh7372_a4mp_domain)
extern void sh7372_add_device_to_domain(struct generic_power_domain *domain,
struct platform_device *pdev);
#else
#define SH7372_A4LC NULL
+#define SH7372_A4PM NULL
static inline void sh7372_add_device_to_domain(struct generic_power_domain *dom,
struct platform_device *pd) {}
Index: linux-2.6/arch/arm/mach-shmobile/board-mackerel.c
===================================================================
--- linux-2.6.orig/arch/arm/mach-shmobile/board-mackerel.c
+++ linux-2.6/arch/arm/mach-shmobile/board-mackerel.c
@@ -1218,6 +1218,8 @@ 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();
}
From: Rafael J. Wysocki <[email protected]>
Allow the generic power domains support code to handle power
domains with multiple states. Replace the .power_down_ok()
callback in struct dev_power_governor with a new callback
.choose_state() that will return the number of the state to put the
power domain into. Add new fields nr_states and current_state to
struct generic_power_domain with the assumption that state 0 will be
the full power state and states 1 through (nr_states - 1) will be low
power. Replace power domain callbacks .power_off() and .power_on()
with a single .set_state() callback taking the number of the state
to put the power domain into as its second argument. Add a new
generic power domain callback .power_off_state() (taking a state
number as its second argument) allowing the core to check if
device runtime PM callbacks need to be executed before putting the
given power domain into the given state. Modify the core power
domains code and the ARM shmobile platform code to take all of the
above changes into account.
Signed-off-by: Rafael J. Wysocki <[email protected]>
---
arch/arm/mach-shmobile/pm-sh7372.c | 11 ++++--
drivers/base/power/domain.c | 60 +++++++++++++++++++++++++++----------
include/linux/pm_domain.h | 20 ++++++++----
3 files changed, 67 insertions(+), 24 deletions(-)
Index: linux-2.6/include/linux/pm_domain.h
===================================================================
--- linux-2.6.orig/include/linux/pm_domain.h
+++ linux-2.6/include/linux/pm_domain.h
@@ -12,7 +12,11 @@
#include <linux/device.h>
struct dev_power_governor {
- bool (*power_down_ok)(struct dev_power_domain *domain);
+ int (*choose_state)(struct dev_power_domain *domain);
+};
+
+struct power_domain_state {
+ void *platform_data;
};
struct generic_power_domain {
@@ -23,12 +27,14 @@ struct generic_power_domain {
struct list_head device_list;
struct mutex lock;
struct dev_power_governor *gov;
- unsigned int in_progress;
+ int nr_states;
+ int current_state;
bool power_is_off;
+ unsigned int in_progress;
unsigned int device_count;
unsigned int suspended_count;
- int (*power_off)(struct dev_power_domain *domain);
- int (*power_on)(struct dev_power_domain *domain);
+ int (*set_state)(struct dev_power_domain *domain, int state);
+ bool (*power_off_state)(struct dev_power_domain *domain, int state);
int (*start_device)(struct device *dev);
int (*stop_device)(struct device *dev);
};
@@ -48,7 +54,8 @@ extern int pm_genpd_add_subdomain(struct
extern int pm_genpd_remove_subdomain(struct generic_power_domain *genpd,
struct generic_power_domain *target);
extern void pm_genpd_init(struct generic_power_domain *genpd,
- struct dev_power_governor *gov, bool is_off);
+ struct dev_power_governor *gov, int nr_states,
+ int cur_state);
#else
static inline int pm_genpd_add_device(struct generic_power_domain *genpd,
struct device *dev)
@@ -71,7 +78,8 @@ static inline int pm_genpd_remove_subdom
return -ENOSYS;
}
static inline void pm_genpd_init(struct generic_power_domain *genpd,
- struct dev_power_governor *gov, bool is_off) {}
+ struct dev_power_governor *gov,
+ int nr_states, int cur_state) {}
#endif
#endif /* _LINUX_PM_DOMAIN_H */
Index: linux-2.6/drivers/base/power/domain.c
===================================================================
--- linux-2.6.orig/drivers/base/power/domain.c
+++ linux-2.6/drivers/base/power/domain.c
@@ -62,6 +62,7 @@ static int __pm_genpd_poweroff(struct ge
struct generic_power_domain *subdomain;
struct dev_list_entry *dle;
unsigned int not_suspended;
+ int new_state;
int ret;
if (genpd->power_is_off)
@@ -83,9 +84,23 @@ static int __pm_genpd_poweroff(struct ge
return ret;
}
- if (genpd->gov && genpd->gov->power_down_ok) {
- if (!genpd->gov->power_down_ok(&genpd->domain))
- return -EAGAIN;
+ new_state = (genpd->gov && genpd->gov->choose_state) ?
+ genpd->gov->choose_state(&genpd->domain) : 1;
+ if (new_state < 0 || new_state >= genpd->nr_states)
+ return -EAGAIN;
+
+ if (new_state == genpd->current_state)
+ return 0;
+
+ if (genpd->power_off_state
+ && !genpd->power_off_state(&genpd->domain, new_state)) {
+ if (genpd->set_state) {
+ ret = genpd->set_state(&genpd->domain, new_state);
+ if (ret)
+ return ret;
+ }
+ genpd->current_state = new_state;
+ return 0;
}
list_for_each_entry_reverse(dle, &genpd->device_list, node) {
@@ -105,9 +120,13 @@ static int __pm_genpd_poweroff(struct ge
goto err_dev;
}
- if (genpd->power_off)
- genpd->power_off(&genpd->domain);
+ if (genpd->set_state) {
+ ret = genpd->set_state(&genpd->domain, new_state);
+ if (ret)
+ goto err_dev;
+ }
+ genpd->current_state = new_state;
genpd->power_is_off = true;
return 0;
@@ -199,14 +218,17 @@ static int __pm_genpd_poweron(struct gen
{
struct dev_list_entry *dle;
- if (!genpd->power_is_off)
+ if (genpd->current_state == 0)
return 0;
- if (genpd->power_on) {
- int ret = genpd->power_on(&genpd->domain);
+ if (genpd->set_state) {
+ int ret = genpd->set_state(&genpd->domain, 0);
if (ret)
return ret;
}
+ genpd->current_state = 0;
+ if (!genpd->power_is_off)
+ return 0;
genpd->power_is_off = false;
@@ -363,8 +385,8 @@ static int pm_genpd_suspend_noirq(struct
mutex_lock(&genpd->lock);
if (++genpd->suspended_count == genpd->device_count) {
- if (genpd->power_off)
- genpd->power_off(&genpd->domain);
+ if (genpd->set_state)
+ genpd->set_state(&genpd->domain, genpd->nr_states - 1);
}
mutex_unlock(&genpd->lock);
@@ -395,8 +417,8 @@ static int pm_genpd_resume_noirq(struct
mutex_lock(&genpd->lock);
if (genpd->suspended_count == genpd->device_count) {
- if (genpd->power_on) {
- int ret = genpd->power_on(&genpd->domain);
+ if (genpd->set_state) {
+ int ret = genpd->set_state(&genpd->domain, 0);
if (ret) {
mutex_unlock(&genpd->lock);
return ret;
@@ -713,10 +735,12 @@ int pm_genpd_remove_subdomain(struct gen
* pm_genpd_init - Initialize a generic I/O power domain object.
* @genpd: Power domain object to initialize.
* @gov: Power domain governor to associate with the domain (may be NULL).
- * @is_off: Initial value of the domain's power_is_off field.
+ * @nr_states: Number of power domain states (must be greater than 1).
+ * @cur_state: Initial state of the power domain.
*/
void pm_genpd_init(struct generic_power_domain *genpd,
- struct dev_power_governor *gov, bool is_off)
+ struct dev_power_governor *gov, int nr_states,
+ int cur_state)
{
if (IS_ERR_OR_NULL(genpd))
return;
@@ -727,8 +751,14 @@ void pm_genpd_init(struct generic_power_
INIT_LIST_HEAD(&genpd->subdomain_list);
mutex_init(&genpd->lock);
genpd->gov = gov;
+ genpd->nr_states = nr_states > 1 ? nr_states : 2;
+ if (cur_state < 0 || cur_state >= nr_states)
+ cur_state = 0;
+ genpd->current_state = cur_state;
+ genpd->power_is_off = genpd->power_off_state ?
+ genpd->power_off_state(&genpd->domain, cur_state) :
+ (cur_state > 0);
genpd->in_progress = 0;
- genpd->power_is_off = is_off;
genpd->device_count = 0;
genpd->suspended_count = 0;
genpd->domain.ops.runtime_suspend = pm_genpd_runtime_suspend;
Index: linux-2.6/arch/arm/mach-shmobile/pm-sh7372.c
===================================================================
--- linux-2.6.orig/arch/arm/mach-shmobile/pm-sh7372.c
+++ linux-2.6/arch/arm/mach-shmobile/pm-sh7372.c
@@ -60,15 +60,20 @@ static int pd_power_up(struct dev_power_
return 0;
}
+static int pd_set_state(struct dev_power_domain *domain, int state)
+{
+ return state > 0 ? pd_power_down(domain) : pd_power_up(domain);
+}
+
static void sh7372_init_domain(struct generic_power_domain *domain,
struct sh7372_domain_data *pdata)
{
- pm_genpd_init(domain, NULL, false);
domain->domain.platform_data = pdata;
domain->stop_device = pm_runtime_clk_suspend;
domain->start_device = pm_runtime_clk_resume;
- domain->power_off = pd_power_down;
- domain->power_on = pd_power_up;
+ domain->set_state = pd_set_state;
+ domain->power_off_state = NULL;
+ pm_genpd_init(domain, NULL, 2, 0);
}
void sh7372_add_device_to_domain(struct generic_power_domain *domain,
From: Rafael J. Wysocki <[email protected]>
Allow the generic power domains support code to handle power
domains with multiple states. Replace the .power_down_ok()
callback in struct dev_power_governor with a new callback
.choose_state() that will return the number of the state to put the
power domain into. Add new fields nr_states and current_state to
struct generic_power_domain with the assumption that state 0 will be
the full power state and states 1 through (nr_states - 1) will be low
power. Replace power domain callbacks .power_off() and .power_on()
with a single .set_state() callback taking the number of the state
to put the power domain into as its second argument. Add a new
generic power domain callback .power_off_state() (taking a state
number as its second argument) allowing the core to check if
device runtime PM callbacks need to be executed before putting the
given power domain into the given state. Modify the core power
domains code and the ARM shmobile platform code to take all of the
above changes into account.
Signed-off-by: Rafael J. Wysocki <[email protected]>
---
arch/arm/mach-shmobile/pm-sh7372.c | 11 ++++--
drivers/base/power/domain.c | 60 +++++++++++++++++++++++++++----------
include/linux/pm_domain.h | 20 ++++++++----
3 files changed, 67 insertions(+), 24 deletions(-)
Index: linux-2.6/include/linux/pm_domain.h
===================================================================
--- linux-2.6.orig/include/linux/pm_domain.h
+++ linux-2.6/include/linux/pm_domain.h
@@ -12,7 +12,11 @@
#include <linux/device.h>
struct dev_power_governor {
- bool (*power_down_ok)(struct dev_power_domain *domain);
+ int (*choose_state)(struct dev_power_domain *domain);
+};
+
+struct power_domain_state {
+ void *platform_data;
};
struct generic_power_domain {
@@ -23,12 +27,14 @@ struct generic_power_domain {
struct list_head device_list;
struct mutex lock;
struct dev_power_governor *gov;
- unsigned int in_progress;
+ int nr_states;
+ int current_state;
bool power_is_off;
+ unsigned int in_progress;
unsigned int device_count;
unsigned int suspended_count;
- int (*power_off)(struct dev_power_domain *domain);
- int (*power_on)(struct dev_power_domain *domain);
+ int (*set_state)(struct dev_power_domain *domain, int state);
+ bool (*power_off_state)(struct dev_power_domain *domain, int state);
int (*start_device)(struct device *dev);
int (*stop_device)(struct device *dev);
};
@@ -48,7 +54,8 @@ extern int pm_genpd_add_subdomain(struct
extern int pm_genpd_remove_subdomain(struct generic_power_domain *genpd,
struct generic_power_domain *target);
extern void pm_genpd_init(struct generic_power_domain *genpd,
- struct dev_power_governor *gov, bool is_off);
+ struct dev_power_governor *gov, int nr_states,
+ int cur_state);
#else
static inline int pm_genpd_add_device(struct generic_power_domain *genpd,
struct device *dev)
@@ -71,7 +78,8 @@ static inline int pm_genpd_remove_subdom
return -ENOSYS;
}
static inline void pm_genpd_init(struct generic_power_domain *genpd,
- struct dev_power_governor *gov, bool is_off) {}
+ struct dev_power_governor *gov,
+ int nr_states, int cur_state) {}
#endif
#endif /* _LINUX_PM_DOMAIN_H */
Index: linux-2.6/drivers/base/power/domain.c
===================================================================
--- linux-2.6.orig/drivers/base/power/domain.c
+++ linux-2.6/drivers/base/power/domain.c
@@ -62,6 +62,7 @@ static int __pm_genpd_poweroff(struct ge
struct generic_power_domain *subdomain;
struct dev_list_entry *dle;
unsigned int not_suspended;
+ int new_state;
int ret;
if (genpd->power_is_off)
@@ -83,9 +84,23 @@ static int __pm_genpd_poweroff(struct ge
return ret;
}
- if (genpd->gov && genpd->gov->power_down_ok) {
- if (!genpd->gov->power_down_ok(&genpd->domain))
- return -EAGAIN;
+ new_state = (genpd->gov && genpd->gov->choose_state) ?
+ genpd->gov->choose_state(&genpd->domain) : 1;
+ if (new_state < 0 || new_state >= genpd->nr_states)
+ return -EAGAIN;
+
+ if (new_state == genpd->current_state)
+ return 0;
+
+ if (genpd->power_off_state
+ && !genpd->power_off_state(&genpd->domain, new_state)) {
+ if (genpd->set_state) {
+ ret = genpd->set_state(&genpd->domain, new_state);
+ if (ret)
+ return ret;
+ }
+ genpd->current_state = new_state;
+ return 0;
}
list_for_each_entry_reverse(dle, &genpd->device_list, node) {
@@ -105,9 +120,13 @@ static int __pm_genpd_poweroff(struct ge
goto err_dev;
}
- if (genpd->power_off)
- genpd->power_off(&genpd->domain);
+ if (genpd->set_state) {
+ ret = genpd->set_state(&genpd->domain, new_state);
+ if (ret)
+ goto err_dev;
+ }
+ genpd->current_state = new_state;
genpd->power_is_off = true;
return 0;
@@ -199,14 +218,17 @@ static int __pm_genpd_poweron(struct gen
{
struct dev_list_entry *dle;
- if (!genpd->power_is_off)
+ if (genpd->current_state == 0)
return 0;
- if (genpd->power_on) {
- int ret = genpd->power_on(&genpd->domain);
+ if (genpd->set_state) {
+ int ret = genpd->set_state(&genpd->domain, 0);
if (ret)
return ret;
}
+ genpd->current_state = 0;
+ if (!genpd->power_is_off)
+ return 0;
genpd->power_is_off = false;
@@ -363,8 +385,8 @@ static int pm_genpd_suspend_noirq(struct
mutex_lock(&genpd->lock);
if (++genpd->suspended_count == genpd->device_count) {
- if (genpd->power_off)
- genpd->power_off(&genpd->domain);
+ if (genpd->set_state)
+ genpd->set_state(&genpd->domain, genpd->nr_states - 1);
}
mutex_unlock(&genpd->lock);
@@ -395,8 +417,8 @@ static int pm_genpd_resume_noirq(struct
mutex_lock(&genpd->lock);
if (genpd->suspended_count == genpd->device_count) {
- if (genpd->power_on) {
- int ret = genpd->power_on(&genpd->domain);
+ if (genpd->set_state) {
+ int ret = genpd->set_state(&genpd->domain, 0);
if (ret) {
mutex_unlock(&genpd->lock);
return ret;
@@ -713,10 +735,12 @@ int pm_genpd_remove_subdomain(struct gen
* pm_genpd_init - Initialize a generic I/O power domain object.
* @genpd: Power domain object to initialize.
* @gov: Power domain governor to associate with the domain (may be NULL).
- * @is_off: Initial value of the domain's power_is_off field.
+ * @nr_states: Number of power domain states (must be greater than 1).
+ * @cur_state: Initial state of the power domain.
*/
void pm_genpd_init(struct generic_power_domain *genpd,
- struct dev_power_governor *gov, bool is_off)
+ struct dev_power_governor *gov, int nr_states,
+ int cur_state)
{
if (IS_ERR_OR_NULL(genpd))
return;
@@ -727,8 +751,14 @@ void pm_genpd_init(struct generic_power_
INIT_LIST_HEAD(&genpd->subdomain_list);
mutex_init(&genpd->lock);
genpd->gov = gov;
+ genpd->nr_states = nr_states > 1 ? nr_states : 2;
+ if (cur_state < 0 || cur_state >= nr_states)
+ cur_state = 0;
+ genpd->current_state = cur_state;
+ genpd->power_is_off = genpd->power_off_state ?
+ genpd->power_off_state(&genpd->domain, cur_state) :
+ (cur_state > 0);
genpd->in_progress = 0;
- genpd->power_is_off = is_off;
genpd->device_count = 0;
genpd->suspended_count = 0;
genpd->domain.ops.runtime_suspend = pm_genpd_runtime_suspend;
Index: linux-2.6/arch/arm/mach-shmobile/pm-sh7372.c
===================================================================
--- linux-2.6.orig/arch/arm/mach-shmobile/pm-sh7372.c
+++ linux-2.6/arch/arm/mach-shmobile/pm-sh7372.c
@@ -60,15 +60,20 @@ static int pd_power_up(struct dev_power_
return 0;
}
+static int pd_set_state(struct dev_power_domain *domain, int state)
+{
+ return state > 0 ? pd_power_down(domain) : pd_power_up(domain);
+}
+
static void sh7372_init_domain(struct generic_power_domain *domain,
struct sh7372_domain_data *pdata)
{
- pm_genpd_init(domain, NULL, false);
domain->domain.platform_data = pdata;
domain->stop_device = pm_runtime_clk_suspend;
domain->start_device = pm_runtime_clk_resume;
- domain->power_off = pd_power_down;
- domain->power_on = pd_power_up;
+ domain->set_state = pd_set_state;
+ domain->power_off_state = NULL;
+ pm_genpd_init(domain, NULL, 2, 0);
}
void sh7372_add_device_to_domain(struct generic_power_domain *domain,
Hi Rafael,
"Rafael J. Wysocki" <[email protected]> writes:
> From: Rafael J. Wysocki <[email protected]>
>
> Allow the generic power domains support code to handle power
> domains with multiple states.
Thanks!
Before I even had the time to respond to you about my previous request
for this kind of feature, you've already implmented it. :)
> Replace the .power_down_ok()
> callback in struct dev_power_governor with a new callback
> .choose_state() that will return the number of the state to put the
> power domain into. Add new fields nr_states and current_state to
> struct generic_power_domain with the assumption that state 0 will be
> the full power state and states 1 through (nr_states - 1) will be low
> power.
> Replace power domain callbacks .power_off() and .power_on()
> with a single .set_state() callback taking the number of the state
> to put the power domain into as its second argument. Add a new
> generic power domain callback .power_off_state() (taking a state
> number as its second argument) allowing the core to check if
> device runtime PM callbacks need to be executed before putting the
> given power domain into the given state. Modify the core power
> domains code and the ARM shmobile platform code to take all of the
> above changes into account.
>
> Signed-off-by: Rafael J. Wysocki <[email protected]>
Reviewed-by: Kevin Hilman <[email protected]>
This looks like a better starting point for more complicated hardware.
Thanks,
Kevin
On Monday, May 16, 2011, Kevin Hilman wrote:
> Hi Rafael,
>
> "Rafael J. Wysocki" <[email protected]> writes:
>
> > From: Rafael J. Wysocki <[email protected]>
> >
> > Allow the generic power domains support code to handle power
> > domains with multiple states.
>
> Thanks!
No problem. :-)
> Before I even had the time to respond to you about my previous request
> for this kind of feature, you've already implmented it. :)
Well, I had the idea how to implement it right after I had replied to
your previous message. The only problem was to find the time to actually
prepare a patch.
> > Replace the .power_down_ok()
> > callback in struct dev_power_governor with a new callback
> > .choose_state() that will return the number of the state to put the
> > power domain into. Add new fields nr_states and current_state to
> > struct generic_power_domain with the assumption that state 0 will be
> > the full power state and states 1 through (nr_states - 1) will be low
> > power.
> > Replace power domain callbacks .power_off() and .power_on()
> > with a single .set_state() callback taking the number of the state
> > to put the power domain into as its second argument. Add a new
> > generic power domain callback .power_off_state() (taking a state
> > number as its second argument) allowing the core to check if
> > device runtime PM callbacks need to be executed before putting the
> > given power domain into the given state. Modify the core power
> > domains code and the ARM shmobile platform code to take all of the
> > above changes into account.
> >
> > Signed-off-by: Rafael J. Wysocki <[email protected]>
>
> Reviewed-by: Kevin Hilman <[email protected]>
Thanks!
> This looks like a better starting point for more complicated hardware.
I agree.
Rafael
Hi,
This is the third update of the patchset adding support for generic I/O power
domains. All patches were posted previously, but I needed to rebase shmobile
patches on top of the current mainline kernel.
>From my perspective, the patches are ready for merging and I'm going put
patches [1-4/5] into my power-domains branch for 2.6.41 (or whatever it turns
out to be) after 2.6.40-rc1 (or whatever it is going to be called) is out.
Patch [5/5] is optional.
The entire patchset has been tested with an ARM shmobile Mackerel board.
[1/5] - Support for generic I/O power domains (runtime PM part).
[2/5] - Support for generic I/O power domains (system sleep part).
[3/5] - Implementation of generic I/O power domains support for SH7372.
[4/5] - [RFC] Support of multiple power domain states.
[5/5] - Introduction of SH7372's A4MP power domain.
Thanks,
Rafael
From: Rafael J. Wysocki <[email protected]>
Introduce common headers, helper functions and callbacks allowing
platforms to use simple generic power domains for runtime power
management.
Introduce struct generic_power_domain to be used for representing
power domains that each contain a number of devices and may be
master 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 <[email protected]>
Acked-by: Greg Kroah-Hartman <[email protected]>
---
drivers/base/power/Makefile | 1
drivers/base/power/domain.c | 446 ++++++++++++++++++++++++++++++++++++++++++++
include/linux/pm.h | 3
include/linux/pm_domain.h | 75 +++++++
kernel/power/Kconfig | 4
5 files changed, 528 insertions(+), 1 deletion(-)
Index: linux-2.6/include/linux/pm_domain.h
===================================================================
--- /dev/null
+++ linux-2.6/include/linux/pm_domain.h
@@ -0,0 +1,75 @@
+/*
+ * pm_domain.h - Definitions and headers related to device power domains.
+ *
+ * Copyright (C) 2011 Rafael J. Wysocki <[email protected]>, Renesas Electronics Corp.
+ *
+ * This file is released under the GPLv2.
+ */
+
+#ifndef _LINUX_PM_DOMAIN_H
+#define _LINUX_PM_DOMAIN_H
+
+#include <linux/device.h>
+
+struct dev_power_governor {
+ bool (*power_down_ok)(struct dev_power_domain *domain);
+};
+
+struct generic_power_domain {
+ struct dev_power_domain domain;
+ struct list_head node;
+ struct generic_power_domain *master;
+ struct list_head subdomain_list;
+ struct list_head device_list;
+ struct mutex lock;
+ struct dev_power_governor *gov;
+ unsigned int in_progress;
+ bool power_is_off;
+ int (*power_off)(struct dev_power_domain *domain);
+ int (*power_on)(struct dev_power_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;
+};
+
+#ifdef CONFIG_PM_GENERIC_DOMAINS
+extern int pm_genpd_add_device(struct generic_power_domain *genpd,
+ struct device *dev);
+extern int pm_genpd_remove_device(struct generic_power_domain *genpd,
+ struct device *dev);
+extern int pm_genpd_add_subdomain(struct generic_power_domain *genpd,
+ struct generic_power_domain *new_subdomain);
+extern int pm_genpd_remove_subdomain(struct generic_power_domain *genpd,
+ struct generic_power_domain *target);
+extern void pm_genpd_init(struct generic_power_domain *genpd,
+ struct dev_power_governor *gov, bool is_off);
+#else
+static inline int pm_genpd_add_device(struct generic_power_domain *genpd,
+ struct device *dev)
+{
+ return -ENOSYS;
+}
+static inline int pm_genpd_remove_device(struct generic_power_domain *genpd,
+ struct device *dev)
+{
+ return -ENOSYS;
+}
+static inline int pm_genpd_add_subdomain(struct generic_power_domain *genpd,
+ struct generic_power_domain *new_sd)
+{
+ return -ENOSYS;
+}
+static inline int pm_genpd_remove_subdomain(struct generic_power_domain *genpd,
+ struct generic_power_domain *target)
+{
+ return -ENOSYS;
+}
+static inline void pm_genpd_init(struct generic_power_domain *genpd,
+ struct dev_power_governor *gov, bool is_off) {}
+#endif
+
+#endif /* _LINUX_PM_DOMAIN_H */
Index: linux-2.6/include/linux/pm.h
===================================================================
--- linux-2.6.orig/include/linux/pm.h
+++ linux-2.6/include/linux/pm.h
@@ -472,7 +472,8 @@ extern void update_pm_runtime_accounting
* subsystem-level and driver-level callbacks.
*/
struct dev_power_domain {
- struct dev_pm_ops ops;
+ struct dev_pm_ops ops;
+ void *platform_data;
};
/*
Index: linux-2.6/drivers/base/power/Makefile
===================================================================
--- linux-2.6.orig/drivers/base/power/Makefile
+++ linux-2.6/drivers/base/power/Makefile
@@ -3,6 +3,7 @@ obj-$(CONFIG_PM_SLEEP) += main.o wakeup.
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
Index: linux-2.6/drivers/base/power/domain.c
===================================================================
--- /dev/null
+++ linux-2.6/drivers/base/power/domain.c
@@ -0,0 +1,446 @@
+/*
+ * drivers/base/power/domain.c - Common code related to device power domains.
+ *
+ * Copyright (C) 2011 Rafael J. Wysocki <[email protected]>, Renesas Electronics Corp.
+ *
+ * This file is released under the GPLv2.
+ */
+
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/io.h>
+#include <linux/pm_runtime.h>
+#include <linux/pm_domain.h>
+#include <linux/slab.h>
+#include <linux/err.h>
+
+#ifdef CONFIG_PM_RUNTIME
+
+/**
+ * __pm_genpd_restore_device - Restore a pre-suspend state of a device.
+ * @dev: Device to restore the state of.
+ * @genpd: Power domain the device belongs to.
+ */
+static void __pm_genpd_restore_device(struct device *dev,
+ struct generic_power_domain *genpd)
+{
+ struct device_driver *drv = dev->driver;
+
+ if (genpd->start_device)
+ genpd->start_device(dev);
+
+ if (drv && drv->pm && drv->pm->runtime_resume)
+ drv->pm->runtime_resume(dev);
+
+ if (genpd->stop_device)
+ genpd->stop_device(dev);
+}
+
+/**
+ * __pm_genpd_poweroff - Remove power from a given power domain.
+ * @genpd: Power 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_power_domain *genpd)
+{
+ struct generic_power_domain *subdomain;
+ struct dev_list_entry *dle;
+ unsigned int not_suspended;
+ int ret;
+
+ if (genpd->power_is_off)
+ return 0;
+
+ not_suspended = 0;
+ list_for_each_entry(dle, &genpd->device_list, node)
+ if (dle->dev->driver && !pm_runtime_suspended(dle->dev))
+ not_suspended++;
+
+ if (not_suspended > genpd->in_progress)
+ return -EBUSY;
+
+ list_for_each_entry_reverse(subdomain, &genpd->subdomain_list, node) {
+ mutex_lock(&subdomain->lock);
+ ret = __pm_genpd_poweroff(subdomain);
+ mutex_unlock(&subdomain->lock);
+ if (ret)
+ return ret;
+ }
+
+ 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->device_list, node) {
+ struct device *dev = dle->dev;
+ struct device_driver *drv = dev->driver;
+
+ if (genpd->start_device)
+ genpd->start_device(dev);
+
+ if (drv && drv->pm && drv->pm->runtime_suspend)
+ ret = drv->pm->runtime_suspend(dev);
+
+ if (genpd->stop_device)
+ genpd->stop_device(dev);
+
+ if (ret)
+ goto err_dev;
+ }
+
+ if (genpd->power_off)
+ genpd->power_off(&genpd->domain);
+
+ genpd->power_is_off = true;
+
+ return 0;
+
+ err_dev:
+ list_for_each_entry_continue(dle, &genpd->device_list, node)
+ __pm_genpd_restore_device(dle->dev, genpd);
+
+ return ret;
+}
+
+/**
+ * pm_genpd_poweroff - Remove power from a given power domain and its masters.
+ * @genpd: Power domain to power down.
+ *
+ * Try to remove power from @genpd and all of its masters in order to save as
+ * much power as possible.
+ */
+static void pm_genpd_poweroff(struct generic_power_domain *genpd)
+{
+ struct generic_power_domain *master;
+
+ mutex_lock(&genpd->lock);
+ master = genpd->master;
+ if (master) {
+ mutex_unlock(&genpd->lock);
+ pm_genpd_poweroff(master);
+ return;
+ }
+ __pm_genpd_poweroff(genpd);
+ mutex_unlock(&genpd->lock);
+}
+
+/**
+ * pm_genpd_runtime_suspend - Suspend a device belonging to I/O power domain.
+ * @dev: Device to suspend.
+ *
+ * Carry out a runtime suspend of a device under the assumption that its
+ * pwr_domain field points to the domain member of an object of type
+ * struct generic_power_domain representing a power domain consisting of I/O
+ * devices.
+ */
+static int pm_genpd_runtime_suspend(struct device *dev)
+{
+ struct generic_power_domain *genpd, *master;
+
+ dev_dbg(dev, "%s()\n", __func__);
+
+ if (IS_ERR_OR_NULL(dev->pwr_domain))
+ return -EINVAL;
+
+ genpd = container_of(dev->pwr_domain,
+ struct generic_power_domain, domain);
+
+ mutex_lock(&genpd->lock);
+
+ if (genpd->stop_device) {
+ int ret = genpd->stop_device(dev);
+ if (ret)
+ goto out;
+ }
+ genpd->in_progress++;
+
+ master = genpd->master;
+ if (master) {
+ mutex_unlock(&genpd->lock);
+
+ pm_genpd_poweroff(master);
+
+ mutex_lock(&genpd->lock);
+ } else {
+ __pm_genpd_poweroff(genpd);
+ }
+
+ genpd->in_progress--;
+
+ out:
+ mutex_unlock(&genpd->lock);
+
+ return 0;
+}
+
+/**
+ * __pm_genpd_poweron - Restore power for a given power domain.
+ * @genpd: Power domain to power up.
+ *
+ * Restore power for @genpd and run runtime resume callbacks provided by all of
+ * its devices' drivers.
+ */
+static int __pm_genpd_poweron(struct generic_power_domain *genpd)
+{
+ struct dev_list_entry *dle;
+
+ if (!genpd->power_is_off)
+ return 0;
+
+ if (genpd->power_on) {
+ int ret = genpd->power_on(&genpd->domain);
+ if (ret)
+ return ret;
+ }
+
+ genpd->power_is_off = false;
+
+ list_for_each_entry(dle, &genpd->device_list, node)
+ __pm_genpd_restore_device(dle->dev, genpd);
+
+ return 0;
+}
+
+/**
+ * pm_genpd_poweron - Restore power for a given power domain and its masters.
+ * @genpd: Power domain to power up.
+ *
+ * Restore power for @genpd and all of its masters so that it is possible to
+ * resume a device belonging to it.
+ */
+static int pm_genpd_poweron(struct generic_power_domain *genpd)
+{
+ struct generic_power_domain *master;
+ int ret;
+
+ mutex_lock(&genpd->lock);
+ master = genpd->master;
+ if (master) {
+ mutex_unlock(&genpd->lock);
+
+ ret = pm_genpd_poweron(master);
+ if (ret)
+ return ret;
+
+ mutex_lock(&genpd->lock);
+ }
+ ret = __pm_genpd_poweron(genpd);
+ mutex_unlock(&genpd->lock);
+
+ return ret;
+}
+
+/**
+ * pm_genpd_runtime_resume - Resume a device belonging to I/O power domain.
+ * @dev: Device to resume.
+ *
+ * Carry out a runtime resume of a device under the assumption that its
+ * pwr_domain field points to the domain member of an object of type
+ * struct generic_power_domain representing a power domain consisting of I/O
+ * devices.
+ */
+static int pm_genpd_runtime_resume(struct device *dev)
+{
+ struct generic_power_domain *genpd;
+ int ret;
+
+ dev_dbg(dev, "%s()\n", __func__);
+
+ if (IS_ERR_OR_NULL(dev->pwr_domain))
+ return -EINVAL;
+
+ genpd = container_of(dev->pwr_domain,
+ struct generic_power_domain, domain);
+
+ ret = pm_genpd_poweron(genpd);
+ if (ret)
+ return ret;
+
+ if (genpd->start_device)
+ genpd->start_device(dev);
+
+ return 0;
+}
+
+#else
+
+#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 power domain.
+ * @genpd: Power domain to add the device to.
+ * @dev: Device to be added.
+ */
+int pm_genpd_add_device(struct generic_power_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);
+
+ list_for_each_entry(dle, &genpd->device_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;
+ list_add_tail(&dle->node, &genpd->device_list);
+
+ spin_lock_irq(&dev->power.lock);
+ dev->pwr_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 power domain.
+ * @genpd: Power domain to remove the device from.
+ * @dev: Device to be removed.
+ */
+int pm_genpd_remove_device(struct generic_power_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->device_list, node) {
+ if (dle->dev != dev)
+ continue;
+
+ spin_lock_irq(&dev->power.lock);
+ dev->pwr_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 power domain.
+ * @genpd: Master power domain to add the subdomain to.
+ * @new_subdomain: Subdomain to be added.
+ */
+int pm_genpd_add_subdomain(struct generic_power_domain *genpd,
+ struct generic_power_domain *new_subdomain)
+{
+ struct generic_power_domain *subdomain;
+ int ret = 0;
+
+ if (IS_ERR_OR_NULL(genpd) || IS_ERR_OR_NULL(new_subdomain))
+ return -EINVAL;
+
+ mutex_lock(&genpd->lock);
+
+ list_for_each_entry(subdomain, &genpd->subdomain_list, node)
+ if (subdomain == new_subdomain) {
+ ret = -EINVAL;
+ goto out;
+ }
+
+ mutex_lock(&new_subdomain->lock);
+ list_add_tail(&new_subdomain->node, &genpd->subdomain_list);
+ new_subdomain->master = genpd;
+ mutex_unlock(&new_subdomain->lock);
+
+ out:
+ mutex_unlock(&genpd->lock);
+
+ return ret;
+}
+
+/**
+ * pm_genpd_remove_subdomain - Remove a subdomain from an I/O power domain.
+ * @genpd: Master power domain to remove the subdomain from.
+ * @target: Subdomain to be removed.
+ */
+int pm_genpd_remove_subdomain(struct generic_power_domain *genpd,
+ struct generic_power_domain *target)
+{
+ struct generic_power_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->subdomain_list, node) {
+ if (subdomain != target)
+ continue;
+
+ mutex_lock(&subdomain->lock);
+ list_del(&subdomain->node);
+ subdomain->master = NULL;
+ mutex_unlock(&subdomain->lock);
+
+ ret = 0;
+ break;
+ }
+
+ mutex_unlock(&genpd->lock);
+
+ return ret;
+}
+
+/**
+ * pm_genpd_init - Initialize a generic I/O power domain object.
+ * @genpd: Power domain object to initialize.
+ * @gov: Power 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_power_domain *genpd,
+ struct dev_power_governor *gov, bool is_off)
+{
+ if (IS_ERR_OR_NULL(genpd))
+ return;
+
+ INIT_LIST_HEAD(&genpd->node);
+ genpd->master = NULL;
+ INIT_LIST_HEAD(&genpd->device_list);
+ INIT_LIST_HEAD(&genpd->subdomain_list);
+ mutex_init(&genpd->lock);
+ genpd->gov = gov;
+ genpd->in_progress = 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;
+}
Index: linux-2.6/kernel/power/Kconfig
===================================================================
--- linux-2.6.orig/kernel/power/Kconfig
+++ linux-2.6/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
From: Rafael J. Wysocki <[email protected]>
Make generic power domains support system-wide power transitions
(system suspend and hibernation). Add suspend, resume, freeze and
thaw callbacks to be associated with struct generic_power_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
power domain ->suspend_noirq() callback stops the device and
eventually removes power from the power domain it belongs to (after
all devices in the power domain have been stopped). During system
resume the power domain ->resume_noirq() callback powers up the power
domain (when executed for the first time for the given power domain)
and starts the device, 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 <[email protected]>
---
drivers/base/power/domain.c | 321 ++++++++++++++++++++++++++++++++++++++++++--
include/linux/pm_domain.h | 2
2 files changed, 315 insertions(+), 8 deletions(-)
Index: linux-2.6/drivers/base/power/domain.c
===================================================================
--- linux-2.6.orig/drivers/base/power/domain.c
+++ linux-2.6/drivers/base/power/domain.c
@@ -14,6 +14,19 @@
#include <linux/slab.h>
#include <linux/err.h>
+#ifdef CONFIG_PM
+
+static struct generic_power_domain *dev_to_genpd(struct device *dev)
+{
+ if (IS_ERR_OR_NULL(dev->pwr_domain))
+ return ERR_PTR(-EINVAL);
+
+ return container_of(dev->pwr_domain,
+ struct generic_power_domain, domain);
+}
+
+#endif /* CONFIG_PM */
+
#ifdef CONFIG_PM_RUNTIME
/**
@@ -143,12 +156,10 @@ static int pm_genpd_runtime_suspend(stru
dev_dbg(dev, "%s()\n", __func__);
- if (IS_ERR_OR_NULL(dev->pwr_domain))
+ genpd = dev_to_genpd(dev);
+ if (IS_ERR(genpd))
return -EINVAL;
- genpd = container_of(dev->pwr_domain,
- struct generic_power_domain, domain);
-
mutex_lock(&genpd->lock);
if (genpd->stop_device) {
@@ -250,12 +261,10 @@ static int pm_genpd_runtime_resume(struc
dev_dbg(dev, "%s()\n", __func__);
- if (IS_ERR_OR_NULL(dev->pwr_domain))
+ genpd = dev_to_genpd(dev);
+ if (IS_ERR(genpd))
return -EINVAL;
- genpd = container_of(dev->pwr_domain,
- struct generic_power_domain, domain);
-
ret = pm_genpd_poweron(genpd);
if (ret)
return ret;
@@ -273,6 +282,284 @@ static int pm_genpd_runtime_resume(struc
#endif /* CONFIG_PM_RUNTIME */
+#ifdef CONFIG_PM_SLEEP
+
+/**
+ * pm_genpd_prepare - Start power transition of a device in a power 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 pwr_domain field points to the domain member of
+ * an object of type struct generic_power_domain representing a power domain
+ * consisting of I/O devices.
+ */
+static int pm_genpd_prepare(struct device *dev)
+{
+ struct generic_power_domain *genpd;
+
+ dev_dbg(dev, "%s()\n", __func__);
+
+ genpd = dev_to_genpd(dev);
+ if (IS_ERR(genpd))
+ return -EINVAL;
+
+ if (genpd->power_is_off)
+ return 0;
+
+ /*
+ * If the device is in the (runtime) "suspended" state, call
+ * ->start_device() for it, if defined.
+ */
+ pm_runtime_resume(dev);
+
+ return pm_generic_prepare(dev);
+}
+
+/**
+ * pm_genpd_suspend - Suspend a device belonging to an I/O power domain.
+ * @dev: Device to suspend.
+ *
+ * Suspend a device under the assumption that its pwr_domain field points to the
+ * domain member of an object of type struct generic_power_domain representing
+ * a power domain consisting of I/O devices.
+ */
+static int pm_genpd_suspend(struct device *dev)
+{
+ struct generic_power_domain *genpd;
+
+ dev_dbg(dev, "%s()\n", __func__);
+
+ genpd = dev_to_genpd(dev);
+ if (IS_ERR(genpd))
+ return -EINVAL;
+
+ return genpd->power_is_off ? 0 : pm_generic_suspend(dev);
+}
+
+/**
+ * pm_genpd_suspend_noirq - Late suspend of a device from an I/O power domain.
+ * @dev: Device to suspend.
+ *
+ * Carry out a late suspend of a device under the assumption that its
+ * pwr_domain field points to the domain member of an object of type
+ * struct generic_power_domain representing a power domain consisting of I/O
+ * devices.
+ */
+static int pm_genpd_suspend_noirq(struct device *dev)
+{
+ struct generic_power_domain *genpd;
+
+ dev_dbg(dev, "%s()\n", __func__);
+
+ genpd = dev_to_genpd(dev);
+ if (IS_ERR(genpd))
+ return -EINVAL;
+
+ if (genpd->power_is_off)
+ return 0;
+
+ if (genpd->stop_device)
+ genpd->stop_device(dev);
+
+ mutex_lock(&genpd->lock);
+ if (++genpd->suspended_count == genpd->device_count) {
+ if (genpd->power_off)
+ genpd->power_off(&genpd->domain);
+ }
+ mutex_unlock(&genpd->lock);
+
+ 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
+ * pwr_domain field points to the domain member of an object of type
+ * struct generic_power_domain representing a power domain consisting of I/O
+ * devices.
+ */
+static int pm_genpd_resume_noirq(struct device *dev)
+{
+ struct generic_power_domain *genpd;
+
+ dev_dbg(dev, "%s()\n", __func__);
+
+ genpd = dev_to_genpd(dev);
+ if (IS_ERR(genpd))
+ return -EINVAL;
+
+ if (genpd->power_is_off)
+ return 0;
+
+ mutex_lock(&genpd->lock);
+ if (genpd->suspended_count == genpd->device_count) {
+ if (genpd->power_on) {
+ int ret = genpd->power_on(&genpd->domain);
+ if (ret) {
+ mutex_unlock(&genpd->lock);
+ return ret;
+ }
+ }
+ }
+ genpd->suspended_count--;
+ mutex_unlock(&genpd->lock);
+
+ if (genpd->start_device)
+ genpd->start_device(dev);
+
+ return 0;
+}
+
+/**
+ * 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 pwr_domain field points to the
+ * domain member of an object of type struct generic_power_domain representing
+ * a power domain consisting of I/O devices.
+ */
+static int pm_genpd_resume(struct device *dev)
+{
+ struct generic_power_domain *genpd;
+
+ dev_dbg(dev, "%s()\n", __func__);
+
+ genpd = dev_to_genpd(dev);
+ if (IS_ERR(genpd))
+ return -EINVAL;
+
+ return genpd->power_is_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 pwr_domain field points to the
+ * domain member of an object of type struct generic_power_domain representing
+ * a power domain consisting of I/O devices.
+ */
+static int pm_genpd_freeze(struct device *dev)
+{
+ struct generic_power_domain *genpd;
+
+ dev_dbg(dev, "%s()\n", __func__);
+
+ genpd = dev_to_genpd(dev);
+ if (IS_ERR(genpd))
+ return -EINVAL;
+
+ return genpd->power_is_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
+ * pwr_domain field points to the domain member of an object of type
+ * struct generic_power_domain representing a power domain consisting of I/O
+ * devices.
+ */
+static int pm_genpd_freeze_noirq(struct device *dev)
+{
+ struct generic_power_domain *genpd;
+
+ dev_dbg(dev, "%s()\n", __func__);
+
+ genpd = dev_to_genpd(dev);
+ if (IS_ERR(genpd))
+ return -EINVAL;
+
+ if (!genpd->power_is_off && 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
+ * pwr_domain field points to the domain member of an object of type
+ * struct generic_power_domain representing a power domain consisting of I/O
+ * devices.
+ */
+static int pm_genpd_thaw_noirq(struct device *dev)
+{
+ struct generic_power_domain *genpd;
+
+ dev_dbg(dev, "%s()\n", __func__);
+
+ genpd = dev_to_genpd(dev);
+ if (IS_ERR(genpd))
+ return -EINVAL;
+
+ if (!genpd->power_is_off && genpd->start_device)
+ genpd->start_device(dev);
+
+ return 0;
+}
+
+/**
+ * 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 pwr_domain field points to the
+ * domain member of an object of type struct generic_power_domain representing
+ * a power domain consisting of I/O devices.
+ */
+static int pm_genpd_thaw(struct device *dev)
+{
+ struct generic_power_domain *genpd;
+
+ dev_dbg(dev, "%s()\n", __func__);
+
+ genpd = dev_to_genpd(dev);
+ if (IS_ERR(genpd))
+ return -EINVAL;
+
+ return genpd->power_is_off ? 0 : pm_generic_thaw(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 pwr_domain field points to the
+ * domain member of an object of type struct generic_power_domain representing
+ * a power domain consisting of I/O devices.
+ */
+static void pm_genpd_complete(struct device *dev)
+{
+ struct generic_power_domain *genpd;
+
+ dev_dbg(dev, "%s()\n", __func__);
+
+ genpd = dev_to_genpd(dev);
+ if (!IS_ERR(genpd) && !genpd->power_is_off)
+ pm_generic_complete(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_complete NULL
+
+#endif /* CONFIG_PM_SLEEP */
+
/**
* pm_genpd_add_device - Add a device to an I/O power domain.
* @genpd: Power domain to add the device to.
@@ -304,6 +591,7 @@ int pm_genpd_add_device(struct generic_p
dle->dev = dev;
list_add_tail(&dle->node, &genpd->device_list);
+ genpd->device_count++;
spin_lock_irq(&dev->power.lock);
dev->pwr_domain = &genpd->domain;
@@ -341,6 +629,7 @@ int pm_genpd_remove_device(struct generi
dev->pwr_domain = NULL;
spin_unlock_irq(&dev->power.lock);
+ genpd->device_count--;
list_del(&dle->node);
kfree(dle);
@@ -440,7 +729,23 @@ void pm_genpd_init(struct generic_power_
genpd->gov = gov;
genpd->in_progress = 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_suspend;
+ genpd->domain.ops.poweroff_noirq = pm_genpd_suspend_noirq;
+ genpd->domain.ops.restore_noirq = pm_genpd_resume_noirq;
+ genpd->domain.ops.restore = pm_genpd_resume;
+ genpd->domain.ops.complete = pm_genpd_complete;
}
Index: linux-2.6/include/linux/pm_domain.h
===================================================================
--- linux-2.6.orig/include/linux/pm_domain.h
+++ linux-2.6/include/linux/pm_domain.h
@@ -25,6 +25,8 @@ struct generic_power_domain {
struct dev_power_governor *gov;
unsigned int in_progress;
bool power_is_off;
+ unsigned int device_count;
+ unsigned int suspended_count;
int (*power_off)(struct dev_power_domain *domain);
int (*power_on)(struct dev_power_domain *domain);
int (*start_device)(struct device *dev);
From: Rafael J. Wysocki <[email protected]>
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 <[email protected]>
---
arch/arm/mach-shmobile/Kconfig | 1
arch/arm/mach-shmobile/Makefile | 4 +
arch/arm/mach-shmobile/board-mackerel.c | 3 +
arch/arm/mach-shmobile/include/mach/sh7372.h | 16 +++++
arch/arm/mach-shmobile/pm-sh7372.c | 81 +++++++++++++++++++++++++++
5 files changed, 105 insertions(+)
Index: linux-2.6/arch/arm/mach-shmobile/board-mackerel.c
===================================================================
--- linux-2.6.orig/arch/arm/mach-shmobile/board-mackerel.c
+++ linux-2.6/arch/arm/mach-shmobile/board-mackerel.c
@@ -1418,6 +1418,9 @@ static void __init mackerel_init(void)
platform_add_devices(mackerel_devices, ARRAY_SIZE(mackerel_devices));
+ 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();
}
Index: linux-2.6/arch/arm/mach-shmobile/include/mach/sh7372.h
===================================================================
--- linux-2.6.orig/arch/arm/mach-shmobile/include/mach/sh7372.h
+++ linux-2.6/arch/arm/mach-shmobile/include/mach/sh7372.h
@@ -12,6 +12,7 @@
#define __ASM_SH7372_H__
#include <linux/sh_clk.h>
+#include <linux/pm_domain.h>
/*
* Pin Function Controller:
@@ -470,4 +471,19 @@ extern struct clk sh7372_fsibck_clk;
extern struct clk sh7372_fsidiva_clk;
extern struct clk sh7372_fsidivb_clk;
+struct platform_device;
+
+#ifdef CONFIG_PM
+extern struct generic_power_domain sh7372_a4lc_domain;
+#define SH7372_A4LC (&sh7372_a4lc_domain)
+
+extern void sh7372_add_device_to_domain(struct generic_power_domain *domain,
+ struct platform_device *pdev);
+#else
+#define SH7372_A4LC NULL
+
+static inline void sh7372_add_device_to_domain(struct generic_power_domain *dom,
+ struct platform_device *pd) {}
+#endif /* CONFIG_PM */
+
#endif /* __ASM_SH7372_H__ */
Index: linux-2.6/arch/arm/mach-shmobile/Makefile
===================================================================
--- linux-2.6.orig/arch/arm/mach-shmobile/Makefile
+++ linux-2.6/arch/arm/mach-shmobile/Makefile
@@ -42,6 +42,10 @@ obj-$(CONFIG_MACH_AP4EVB) += board-ap4ev
obj-$(CONFIG_MACH_AG5EVM) += board-ag5evm.o
obj-$(CONFIG_MACH_MACKEREL) += board-mackerel.o
+# PM objects
+pm-$(CONFIG_ARCH_SH7372) += pm-sh7372.o
+
# Framework support
obj-$(CONFIG_SMP) += $(smp-y)
obj-$(CONFIG_GENERIC_GPIO) += $(pfc-y)
+obj-$(CONFIG_PM) += $(pm-y)
Index: linux-2.6/arch/arm/mach-shmobile/Kconfig
===================================================================
--- linux-2.6.orig/arch/arm/mach-shmobile/Kconfig
+++ linux-2.6/arch/arm/mach-shmobile/Kconfig
@@ -19,6 +19,7 @@ config ARCH_SH7372
select CPU_V7
select SH_CLK_CPG
select ARCH_WANT_OPTIONAL_GPIOLIB
+ select PM_GENERIC_DOMAINS
config ARCH_SH73A0
bool "SH-Mobile AG5 (R8A73A00)"
Index: linux-2.6/arch/arm/mach-shmobile/pm-sh7372.c
===================================================================
--- linux-2.6.orig/arch/arm/mach-shmobile/pm-sh7372.c
+++ linux-2.6/arch/arm/mach-shmobile/pm-sh7372.c
@@ -15,16 +15,97 @@
#include <linux/list.h>
#include <linux/err.h>
#include <linux/slab.h>
+#include <linux/pm_runtime.h>
+#include <linux/platform_device.h>
#include <asm/system.h>
#include <asm/io.h>
#include <asm/tlbflush.h>
#include <mach/common.h>
+#include <mach/sh7372.h>
#define SMFRAM 0xe6a70000
#define SYSTBCR 0xe6150024
#define SBAR 0xe6180020
#define APARMBAREA 0xe6f10020
+#define SPDCR 0xe6180008
+#define SWUCR 0xe6180014
+#define PSTR 0xe6180080
+
+struct sh7372_domain_data {
+ unsigned int bit_shift;
+};
+
+static int pd_power_down(struct dev_power_domain *domain)
+{
+ struct sh7372_domain_data *dd = domain->platform_data;
+ unsigned int mask = 1 << dd->bit_shift;
+
+ if (__raw_readl(PSTR) & mask) {
+ __raw_writel(mask, SPDCR);
+
+ while (__raw_readl(SPDCR) & mask) {}
+
+ pr_debug("sh7372 power domain down 0x%08x -> PSTR = 0x%08x\n",
+ mask, __raw_readl(PSTR));
+ }
+
+ return 0;
+}
+
+static int pd_power_up(struct dev_power_domain *domain)
+{
+ struct sh7372_domain_data *dd = domain->platform_data;
+ unsigned int mask = 1 << dd->bit_shift;
+
+ if (!(__raw_readl(PSTR) & mask)) {
+ __raw_writel(mask, SWUCR);
+
+ while (__raw_readl(SWUCR) & mask) {}
+
+ pr_debug("sh7372 power domain up 0x%08x -> PSTR = 0x%08x\n",
+ mask, __raw_readl(PSTR));
+ }
+
+ return 0;
+}
+
+static void sh7372_init_domain(struct generic_power_domain *domain,
+ struct sh7372_domain_data *pdata)
+{
+ pm_genpd_init(domain, NULL, false);
+ domain->domain.platform_data = pdata;
+ domain->stop_device = pm_runtime_clk_suspend;
+ domain->start_device = pm_runtime_clk_resume;
+ domain->power_off = pd_power_down;
+ domain->power_on = pd_power_up;
+}
+
+void sh7372_add_device_to_domain(struct generic_power_domain *domain,
+ struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+
+ if (!dev->power.subsys_data) {
+ pm_runtime_clk_init(dev);
+ pm_runtime_clk_add(dev, NULL);
+ }
+ pm_genpd_add_device(domain, dev);
+}
+
+static struct sh7372_domain_data sh7372_a4lc_domain_data = {
+ .bit_shift = 1,
+};
+
+struct generic_power_domain sh7372_a4lc_domain;
+
+static int __init sh7372_power_domains_init(void)
+{
+ sh7372_init_domain(&sh7372_a4lc_domain, &sh7372_a4lc_domain_data);
+ return 0;
+}
+core_initcall(sh7372_power_domains_init);
+
static void sh7372_enter_core_standby(void)
{
void __iomem *smfram = (void __iomem *)SMFRAM;
From: Rafael J. Wysocki <[email protected]>
Allow the generic power domains support code to handle power
domains with multiple states. Replace the .power_down_ok()
callback in struct dev_power_governor with a new callback
.choose_state() that will return the number of the state to put the
power domain into. Add new fields nr_states and current_state to
struct generic_power_domain with the assumption that state 0 will be
the full power state and states 1 through (nr_states - 1) will be low
power. Replace power domain callbacks .power_off() and .power_on()
with a single .set_state() callback taking the number of the state
to put the power domain into as its second argument. Add a new
generic power domain callback .power_off_state() (taking a state
number as its second argument) allowing the core to check if
device runtime PM callbacks need to be executed before putting the
given power domain into the given state. Modify the core power
domains code and the ARM shmobile platform code to take all of the
above changes into account.
Signed-off-by: Rafael J. Wysocki <[email protected]>
Reviewed-by: Kevin Hilman <[email protected]>
---
arch/arm/mach-shmobile/pm-sh7372.c | 11 ++++--
drivers/base/power/domain.c | 60 +++++++++++++++++++++++++++----------
include/linux/pm_domain.h | 16 ++++++---
3 files changed, 63 insertions(+), 24 deletions(-)
Index: linux-2.6/include/linux/pm_domain.h
===================================================================
--- linux-2.6.orig/include/linux/pm_domain.h
+++ linux-2.6/include/linux/pm_domain.h
@@ -12,7 +12,7 @@
#include <linux/device.h>
struct dev_power_governor {
- bool (*power_down_ok)(struct dev_power_domain *domain);
+ int (*choose_state)(struct dev_power_domain *domain);
};
struct generic_power_domain {
@@ -23,12 +23,14 @@ struct generic_power_domain {
struct list_head device_list;
struct mutex lock;
struct dev_power_governor *gov;
- unsigned int in_progress;
+ int nr_states;
+ int current_state;
bool power_is_off;
+ unsigned int in_progress;
unsigned int device_count;
unsigned int suspended_count;
- int (*power_off)(struct dev_power_domain *domain);
- int (*power_on)(struct dev_power_domain *domain);
+ int (*set_state)(struct dev_power_domain *domain, int state);
+ bool (*power_off_state)(struct dev_power_domain *domain, int state);
int (*start_device)(struct device *dev);
int (*stop_device)(struct device *dev);
};
@@ -48,7 +50,8 @@ extern int pm_genpd_add_subdomain(struct
extern int pm_genpd_remove_subdomain(struct generic_power_domain *genpd,
struct generic_power_domain *target);
extern void pm_genpd_init(struct generic_power_domain *genpd,
- struct dev_power_governor *gov, bool is_off);
+ struct dev_power_governor *gov, int nr_states,
+ int cur_state);
#else
static inline int pm_genpd_add_device(struct generic_power_domain *genpd,
struct device *dev)
@@ -71,7 +74,8 @@ static inline int pm_genpd_remove_subdom
return -ENOSYS;
}
static inline void pm_genpd_init(struct generic_power_domain *genpd,
- struct dev_power_governor *gov, bool is_off) {}
+ struct dev_power_governor *gov,
+ int nr_states, int cur_state) {}
#endif
#endif /* _LINUX_PM_DOMAIN_H */
Index: linux-2.6/drivers/base/power/domain.c
===================================================================
--- linux-2.6.orig/drivers/base/power/domain.c
+++ linux-2.6/drivers/base/power/domain.c
@@ -62,6 +62,7 @@ static int __pm_genpd_poweroff(struct ge
struct generic_power_domain *subdomain;
struct dev_list_entry *dle;
unsigned int not_suspended;
+ int new_state;
int ret;
if (genpd->power_is_off)
@@ -83,9 +84,23 @@ static int __pm_genpd_poweroff(struct ge
return ret;
}
- if (genpd->gov && genpd->gov->power_down_ok) {
- if (!genpd->gov->power_down_ok(&genpd->domain))
- return -EAGAIN;
+ new_state = (genpd->gov && genpd->gov->choose_state) ?
+ genpd->gov->choose_state(&genpd->domain) : 1;
+ if (new_state < 0 || new_state >= genpd->nr_states)
+ return -EAGAIN;
+
+ if (new_state == genpd->current_state)
+ return 0;
+
+ if (genpd->power_off_state
+ && !genpd->power_off_state(&genpd->domain, new_state)) {
+ if (genpd->set_state) {
+ ret = genpd->set_state(&genpd->domain, new_state);
+ if (ret)
+ return ret;
+ }
+ genpd->current_state = new_state;
+ return 0;
}
list_for_each_entry_reverse(dle, &genpd->device_list, node) {
@@ -105,9 +120,13 @@ static int __pm_genpd_poweroff(struct ge
goto err_dev;
}
- if (genpd->power_off)
- genpd->power_off(&genpd->domain);
+ if (genpd->set_state) {
+ ret = genpd->set_state(&genpd->domain, new_state);
+ if (ret)
+ goto err_dev;
+ }
+ genpd->current_state = new_state;
genpd->power_is_off = true;
return 0;
@@ -199,14 +218,17 @@ static int __pm_genpd_poweron(struct gen
{
struct dev_list_entry *dle;
- if (!genpd->power_is_off)
+ if (genpd->current_state == 0)
return 0;
- if (genpd->power_on) {
- int ret = genpd->power_on(&genpd->domain);
+ if (genpd->set_state) {
+ int ret = genpd->set_state(&genpd->domain, 0);
if (ret)
return ret;
}
+ genpd->current_state = 0;
+ if (!genpd->power_is_off)
+ return 0;
genpd->power_is_off = false;
@@ -363,8 +385,8 @@ static int pm_genpd_suspend_noirq(struct
mutex_lock(&genpd->lock);
if (++genpd->suspended_count == genpd->device_count) {
- if (genpd->power_off)
- genpd->power_off(&genpd->domain);
+ if (genpd->set_state)
+ genpd->set_state(&genpd->domain, genpd->nr_states - 1);
}
mutex_unlock(&genpd->lock);
@@ -395,8 +417,8 @@ static int pm_genpd_resume_noirq(struct
mutex_lock(&genpd->lock);
if (genpd->suspended_count == genpd->device_count) {
- if (genpd->power_on) {
- int ret = genpd->power_on(&genpd->domain);
+ if (genpd->set_state) {
+ int ret = genpd->set_state(&genpd->domain, 0);
if (ret) {
mutex_unlock(&genpd->lock);
return ret;
@@ -713,10 +735,12 @@ int pm_genpd_remove_subdomain(struct gen
* pm_genpd_init - Initialize a generic I/O power domain object.
* @genpd: Power domain object to initialize.
* @gov: Power domain governor to associate with the domain (may be NULL).
- * @is_off: Initial value of the domain's power_is_off field.
+ * @nr_states: Number of power domain states (must be greater than 1).
+ * @cur_state: Initial state of the power domain.
*/
void pm_genpd_init(struct generic_power_domain *genpd,
- struct dev_power_governor *gov, bool is_off)
+ struct dev_power_governor *gov, int nr_states,
+ int cur_state)
{
if (IS_ERR_OR_NULL(genpd))
return;
@@ -727,8 +751,14 @@ void pm_genpd_init(struct generic_power_
INIT_LIST_HEAD(&genpd->subdomain_list);
mutex_init(&genpd->lock);
genpd->gov = gov;
+ genpd->nr_states = nr_states > 1 ? nr_states : 2;
+ if (cur_state < 0 || cur_state >= nr_states)
+ cur_state = 0;
+ genpd->current_state = cur_state;
+ genpd->power_is_off = genpd->power_off_state ?
+ genpd->power_off_state(&genpd->domain, cur_state) :
+ (cur_state > 0);
genpd->in_progress = 0;
- genpd->power_is_off = is_off;
genpd->device_count = 0;
genpd->suspended_count = 0;
genpd->domain.ops.runtime_suspend = pm_genpd_runtime_suspend;
Index: linux-2.6/arch/arm/mach-shmobile/pm-sh7372.c
===================================================================
--- linux-2.6.orig/arch/arm/mach-shmobile/pm-sh7372.c
+++ linux-2.6/arch/arm/mach-shmobile/pm-sh7372.c
@@ -70,15 +70,20 @@ static int pd_power_up(struct dev_power_
return 0;
}
+static int pd_set_state(struct dev_power_domain *domain, int state)
+{
+ return state > 0 ? pd_power_down(domain) : pd_power_up(domain);
+}
+
static void sh7372_init_domain(struct generic_power_domain *domain,
struct sh7372_domain_data *pdata)
{
- pm_genpd_init(domain, NULL, false);
domain->domain.platform_data = pdata;
domain->stop_device = pm_runtime_clk_suspend;
domain->start_device = pm_runtime_clk_resume;
- domain->power_off = pd_power_down;
- domain->power_on = pd_power_up;
+ domain->set_state = pd_set_state;
+ domain->power_off_state = NULL;
+ pm_genpd_init(domain, NULL, 2, 0);
}
void sh7372_add_device_to_domain(struct generic_power_domain *domain,
From: Rafael J. Wysocki <[email protected]>
Use the power domains support code added by the previous
patches to implement support for power domain A4MP on SH7372.
Signed-off-by: Rafael J. Wysocki <[email protected]>
---
arch/arm/mach-shmobile/board-mackerel.c | 2 ++
arch/arm/mach-shmobile/include/mach/sh7372.h | 3 +++
arch/arm/mach-shmobile/pm-sh7372.c | 7 +++++++
3 files changed, 12 insertions(+)
Index: linux-2.6/arch/arm/mach-shmobile/pm-sh7372.c
===================================================================
--- linux-2.6.orig/arch/arm/mach-shmobile/pm-sh7372.c
+++ linux-2.6/arch/arm/mach-shmobile/pm-sh7372.c
@@ -104,9 +104,16 @@ static struct sh7372_domain_data sh7372_
struct generic_power_domain sh7372_a4lc_domain;
+static struct sh7372_domain_data sh7372_a4mp_domain_data = {
+ .bit_shift = 2,
+};
+
+struct generic_power_domain sh7372_a4mp_domain;
+
static int __init sh7372_power_domains_init(void)
{
sh7372_init_domain(&sh7372_a4lc_domain, &sh7372_a4lc_domain_data);
+ sh7372_init_domain(&sh7372_a4mp_domain, &sh7372_a4mp_domain_data);
return 0;
}
core_initcall(sh7372_power_domains_init);
Index: linux-2.6/arch/arm/mach-shmobile/include/mach/sh7372.h
===================================================================
--- linux-2.6.orig/arch/arm/mach-shmobile/include/mach/sh7372.h
+++ linux-2.6/arch/arm/mach-shmobile/include/mach/sh7372.h
@@ -476,11 +476,14 @@ struct platform_device;
#ifdef CONFIG_PM
extern struct generic_power_domain sh7372_a4lc_domain;
#define SH7372_A4LC (&sh7372_a4lc_domain)
+extern struct generic_power_domain sh7372_a4mp_domain;
+#define SH7372_A4MP (&sh7372_a4mp_domain)
extern void sh7372_add_device_to_domain(struct generic_power_domain *domain,
struct platform_device *pdev);
#else
#define SH7372_A4LC NULL
+#define SH7372_A4PM NULL
static inline void sh7372_add_device_to_domain(struct generic_power_domain *dom,
struct platform_device *pd) {}
Index: linux-2.6/arch/arm/mach-shmobile/board-mackerel.c
===================================================================
--- linux-2.6.orig/arch/arm/mach-shmobile/board-mackerel.c
+++ linux-2.6/arch/arm/mach-shmobile/board-mackerel.c
@@ -1421,6 +1421,8 @@ 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();
}
On Sat, 28 May 2011, Rafael J. Wysocki wrote:
> From: Rafael J. Wysocki <[email protected]>
>
> Introduce common headers, helper functions and callbacks allowing
> platforms to use simple generic power domains for runtime power
> management.
>
> Introduce struct generic_power_domain to be used for representing
> power domains that each contain a number of devices and may be
> master 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).
I've got a generic question to this: do we have any examples of
power-domain specific ->start_device() and ->stop_device() callbacks? A
common case is, when these callbacks start and stop clocks, associated
with the device, but this is not power-domain specific, right? Do we have
any examples of different power domains in a system, having different
these calbacks, but all devices in one power-domain, having the same ones?
Thanks
Guennadi
---
Guennadi Liakhovetski, Ph.D.
Freelance Open-Source Software Developer
http://www.open-technology.de/
On Thursday, June 02, 2011, Guennadi Liakhovetski wrote:
> On Sat, 28 May 2011, Rafael J. Wysocki wrote:
>
> > From: Rafael J. Wysocki <[email protected]>
> >
> > Introduce common headers, helper functions and callbacks allowing
> > platforms to use simple generic power domains for runtime power
> > management.
> >
> > Introduce struct generic_power_domain to be used for representing
> > power domains that each contain a number of devices and may be
> > master 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).
>
> I've got a generic question to this: do we have any examples of
> power-domain specific ->start_device() and ->stop_device() callbacks? A
> common case is, when these callbacks start and stop clocks, associated
> with the device, but this is not power-domain specific, right? Do we have
> any examples of different power domains in a system, having different
> these calbacks, but all devices in one power-domain, having the same ones?
No, we don't have any at the moment.
Thanks,
Rafael