[PATCH v4 07/17] drivers: base: Port power management code from Linux

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Port an extremely abridged version of power management/power domain
code from Linux as a dependency of i.MX7D PCIe work. Currenlty only
bare minimum of functionality is implemented.

Signed-off-by: Andrey Smirnov <andrew.smirnov@xxxxxxxxx>
---
 drivers/Kconfig         |   1 +
 drivers/base/Kconfig    |   3 +
 drivers/base/Makefile   |   4 +-
 drivers/base/platform.c |   7 ++
 drivers/base/power.c    | 249 ++++++++++++++++++++++++++++++++++++++++
 include/pm_domain.h     |  82 +++++++++++++
 6 files changed, 345 insertions(+), 1 deletion(-)
 create mode 100644 drivers/base/Kconfig
 create mode 100644 drivers/base/power.c
 create mode 100644 include/pm_domain.h

diff --git a/drivers/Kconfig b/drivers/Kconfig
index 1e0246da6d..c3bf9dfe18 100644
--- a/drivers/Kconfig
+++ b/drivers/Kconfig
@@ -1,5 +1,6 @@
 menu "Drivers"
 
+source "drivers/base/Kconfig"
 source "drivers/efi/Kconfig"
 source "drivers/of/Kconfig"
 source "drivers/aiodev/Kconfig"
diff --git a/drivers/base/Kconfig b/drivers/base/Kconfig
new file mode 100644
index 0000000000..1e13e5ed9d
--- /dev/null
+++ b/drivers/base/Kconfig
@@ -0,0 +1,3 @@
+
+config PM_GENERIC_DOMAINS
+	bool
diff --git a/drivers/base/Makefile b/drivers/base/Makefile
index 4bd4217745..6d2cef8e1a 100644
--- a/drivers/base/Makefile
+++ b/drivers/base/Makefile
@@ -2,4 +2,6 @@ obj-y	+= bus.o
 obj-y	+= driver.o
 obj-y	+= platform.o
 obj-y	+= resource.o
-obj-y	+= regmap/
\ No newline at end of file
+obj-y	+= regmap/
+
+obj-$(CONFIG_PM_GENERIC_DOMAINS) += power.o
diff --git a/drivers/base/platform.c b/drivers/base/platform.c
index 85bdfb0149..1d3fa2eb44 100644
--- a/drivers/base/platform.c
+++ b/drivers/base/platform.c
@@ -21,9 +21,16 @@
 #include <errno.h>
 #include <init.h>
 #include <of.h>
+#include <pm_domain.h>
 
 static int platform_probe(struct device_d *dev)
 {
+	int ret;
+
+	ret = genpd_dev_pm_attach(dev);
+	if (ret < 0)
+		return ret;
+
 	return dev->driver->probe(dev);
 }
 
diff --git a/drivers/base/power.c b/drivers/base/power.c
new file mode 100644
index 0000000000..12674ca7d9
--- /dev/null
+++ b/drivers/base/power.c
@@ -0,0 +1,249 @@
+#include <common.h>
+#include <driver.h>
+#include <errno.h>
+#include <of.h>
+
+#include <pm_domain.h>
+
+#define genpd_status_on(genpd)		(genpd->status == GPD_STATE_ACTIVE)
+
+static LIST_HEAD(gpd_list);
+
+/**
+ * pm_genpd_init - Initialize a generic I/O PM domain object.
+ * @genpd: PM domain object to initialize.
+ * @gov: PM domain governor to associate with the domain (may be NULL).
+ * @is_off: Initial value of the domain's power_is_off field.
+ *
+ * Returns 0 on successful initialization, else a negative error code.
+ */
+int pm_genpd_init(struct generic_pm_domain *genpd, void *gov, bool is_off)
+{
+	if (IS_ERR_OR_NULL(genpd))
+		return -EINVAL;
+
+	genpd->status = is_off ? GPD_STATE_POWER_OFF : GPD_STATE_ACTIVE;
+
+	list_add(&genpd->gpd_list_node, &gpd_list);
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(pm_genpd_init);
+
+/**
+ * struct of_genpd_provider - PM domain provider registration structure
+ * @link: Entry in global list of PM domain providers
+ * @node: Pointer to device tree node of PM domain provider
+ * @xlate: Provider-specific xlate callback mapping a set of specifier cells
+ *         into a PM domain.
+ * @data: context pointer to be passed into @xlate callback
+ */
+struct of_genpd_provider {
+	struct list_head link;
+	struct device_node *node;
+	genpd_xlate_t xlate;
+	void *data;
+};
+
+/* List of registered PM domain providers. */
+static LIST_HEAD(of_genpd_providers);
+
+static bool genpd_present(const struct generic_pm_domain *genpd)
+{
+	const struct generic_pm_domain *gpd;
+
+	if (IS_ERR_OR_NULL(genpd))
+		return false;
+
+	list_for_each_entry(gpd, &gpd_list, gpd_list_node)
+		if (gpd == genpd)
+			return true;
+
+	return false;
+}
+
+/**
+ * genpd_xlate_simple() - Xlate function for direct node-domain mapping
+ * @genpdspec: OF phandle args to map into a PM domain
+ * @data: xlate function private data - pointer to struct generic_pm_domain
+ *
+ * This is a generic xlate function that can be used to model PM domains that
+ * have their own device tree nodes. The private data of xlate function needs
+ * to be a valid pointer to struct generic_pm_domain.
+ */
+static struct generic_pm_domain *genpd_xlate_simple(
+					struct of_phandle_args *genpdspec,
+					void *data)
+{
+	return data;
+}
+
+/**
+ * genpd_add_provider() - Register a PM domain provider for a node
+ * @np: Device node pointer associated with the PM domain provider.
+ * @xlate: Callback for decoding PM domain from phandle arguments.
+ * @data: Context pointer for @xlate callback.
+ */
+static int genpd_add_provider(struct device_node *np, genpd_xlate_t xlate,
+			      void *data)
+{
+	struct of_genpd_provider *cp;
+
+	cp = kzalloc(sizeof(*cp), GFP_KERNEL);
+	if (!cp)
+		return -ENOMEM;
+
+	cp->node  = np;
+	cp->data  = data;
+	cp->xlate = xlate;
+
+	list_add(&cp->link, &of_genpd_providers);
+	pr_debug("Added domain provider from %pOF\n", np);
+
+	return 0;
+}
+
+/**
+ * of_genpd_add_provider_simple() - Register a simple PM domain provider
+ * @np: Device node pointer associated with the PM domain provider.
+ * @genpd: Pointer to PM domain associated with the PM domain provider.
+ */
+int of_genpd_add_provider_simple(struct device_node *np,
+				 struct generic_pm_domain *genpd)
+{
+	int ret = -EINVAL;
+
+	if (!np || !genpd)
+		return -EINVAL;
+
+	if (genpd_present(genpd))
+		ret = genpd_add_provider(np, genpd_xlate_simple, genpd);
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(of_genpd_add_provider_simple);
+
+/**
+ * genpd_get_from_provider() - Look-up PM domain
+ * @genpdspec: OF phandle args to use for look-up
+ *
+ * Looks for a PM domain provider under the node specified by @genpdspec and if
+ * found, uses xlate function of the provider to map phandle args to a PM
+ * domain.
+ *
+ * Returns a valid pointer to struct generic_pm_domain on success or ERR_PTR()
+ * on failure.
+ */
+static struct generic_pm_domain *genpd_get_from_provider(
+					struct of_phandle_args *genpdspec)
+{
+	struct generic_pm_domain *genpd = ERR_PTR(-ENOENT);
+	struct of_genpd_provider *provider;
+
+	if (!genpdspec)
+		return ERR_PTR(-EINVAL);
+
+	/* Check if we have such a provider in our array */
+	list_for_each_entry(provider, &of_genpd_providers, link) {
+		if (provider->node == genpdspec->np)
+			genpd = provider->xlate(genpdspec, provider->data);
+		if (!IS_ERR(genpd))
+			break;
+	}
+
+	return genpd;
+}
+
+static int _genpd_power_on(struct generic_pm_domain *genpd, bool timed)
+{
+	if (!genpd->power_on)
+		return 0;
+
+	return genpd->power_on(genpd);
+}
+
+/**
+ * genpd_power_on - Restore power to a given PM domain and its masters.
+ * @genpd: PM domain to power up.
+ * @depth: nesting count for lockdep.
+ *
+ * Restore power to @genpd and all of its masters so that it is possible to
+ * resume a device belonging to it.
+ */
+static int genpd_power_on(struct generic_pm_domain *genpd, unsigned int depth)
+{
+	int ret;
+
+	if (genpd_status_on(genpd))
+		return 0;
+
+	ret = _genpd_power_on(genpd, true);
+	if (ret)
+		return ret;
+
+	genpd->status = GPD_STATE_ACTIVE;
+
+	return 0;
+}
+
+static int __genpd_dev_pm_attach(struct device_d *dev, struct device_node *np,
+				 unsigned int index, bool power_on)
+{
+	struct of_phandle_args pd_args;
+	struct generic_pm_domain *pd;
+	int ret;
+
+	ret = of_parse_phandle_with_args(np, "power-domains",
+				"#power-domain-cells", index, &pd_args);
+	if (ret < 0)
+		return ret;
+
+	pd = genpd_get_from_provider(&pd_args);
+	if (IS_ERR(pd)) {
+		ret = PTR_ERR(pd);
+		dev_dbg(dev, "%s() failed to find PM domain: %d\n",
+			__func__, ret);
+		/*
+		 * Assume that missing genpds are unresolved
+		 * dependency are report them as deferred
+		 */
+		return (ret == -ENOENT) ? -EPROBE_DEFER : ret;
+	}
+
+	dev_dbg(dev, "adding to PM domain %s\n", pd->name);
+
+	if (power_on)
+		ret = genpd_power_on(pd, 0);
+
+	return ret ?: 1;
+}
+
+/**
+ * genpd_dev_pm_attach - Attach a device to its PM domain using DT.
+ * @dev: Device to attach.
+ *
+ * Parse device's OF node to find a PM domain specifier. If such is found,
+ * attaches the device to retrieved pm_domain ops.
+ *
+ * Returns 1 on successfully attached PM domain, 0 when the device don't need a
+ * PM domain or when multiple power-domains exists for it, else a negative error
+ * code. Note that if a power-domain exists for the device, but it cannot be
+ * found or turned on, then return -EPROBE_DEFER to ensure that the device is
+ * not probed and to re-try again later.
+ */
+int genpd_dev_pm_attach(struct device_d *dev)
+{
+	if (!dev->device_node)
+		return 0;
+
+	/*
+	 * Devices with multiple PM domains must be attached separately, as we
+	 * can only attach one PM domain per device.
+	 */
+	if (of_count_phandle_with_args(dev->device_node, "power-domains",
+				       "#power-domain-cells") != 1)
+		return 0;
+
+	return __genpd_dev_pm_attach(dev, dev->device_node, 0, true);
+}
+EXPORT_SYMBOL_GPL(genpd_dev_pm_attach);
diff --git a/include/pm_domain.h b/include/pm_domain.h
new file mode 100644
index 0000000000..6d59587ece
--- /dev/null
+++ b/include/pm_domain.h
@@ -0,0 +1,82 @@
+#ifndef _PM_DOMAIN_H
+#define _PM_DOMAIN_H
+
+enum gpd_status {
+	GPD_STATE_ACTIVE = 0,	/* PM domain is active */
+	GPD_STATE_POWER_OFF,	/* PM domain is off */
+};
+
+struct generic_pm_domain {
+	const char *name;
+	struct list_head gpd_list_node;	/* Node in the global PM domains list */
+
+	enum gpd_status status;	/* Current state of the domain */
+
+	int (*power_off)(struct generic_pm_domain *domain);
+	int (*power_on)(struct generic_pm_domain *domain);
+};
+
+typedef struct generic_pm_domain *(*genpd_xlate_t)(struct of_phandle_args *args,
+						   void *data);
+
+#ifdef CONFIG_PM_GENERIC_DOMAINS
+
+int genpd_dev_pm_attach(struct device_d *dev);
+
+/**
+ * dev_pm_domain_attach - Attach a device to its PM domain.
+ * @dev: Device to attach.
+ * @power_on: Used to indicate whether we should power on the device.
+ *
+ * The @dev may only be attached to a single PM domain. By iterating through
+ * the available alternatives we try to find a valid PM domain for the device.
+ * As attachment succeeds, the ->detach() callback in the struct dev_pm_domain
+ * should be assigned by the corresponding attach function.
+ *
+ * This function should typically be invoked from subsystem level code during
+ * the probe phase. Especially for those that holds devices which requires
+ * power management through PM domains.
+ *
+ * Callers must ensure proper synchronization of this function with power
+ * management callbacks.
+ *
+ * Returns 0 on successfully attached PM domain or negative error code.
+ */
+static inline int dev_pm_domain_attach(struct device_d *dev, bool power_on)
+{
+	return genpd_dev_pm_attach(dev);
+}
+
+int pm_genpd_init(struct generic_pm_domain *genpd, void *gov, bool is_off);
+
+int of_genpd_add_provider_simple(struct device_node *np,
+				 struct generic_pm_domain *genpd);
+
+#else
+
+static inline int pm_genpd_init(struct generic_pm_domain *genpd,
+				void *gov, bool is_off)
+{
+	return -ENOSYS;
+}
+
+static inline int genpd_dev_pm_attach(struct device_d *dev)
+{
+	return 0;
+}
+
+static inline int dev_pm_domain_attach(struct device_d *dev, bool power_on)
+{
+	return 0;
+}
+
+static inline int
+of_genpd_add_provider_simple(struct device_node *np,
+			     struct generic_pm_domain *genpd)
+{
+	return -ENOTSUPP;
+}
+
+#endif
+
+#endif
\ No newline at end of file
-- 
2.20.1


_______________________________________________
barebox mailing list
barebox@xxxxxxxxxxxxxxxxxxx
http://lists.infradead.org/mailman/listinfo/barebox



[Index of Archives]     [Linux Embedded]     [Linux USB Devel]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]     [XFree86]

  Powered by Linux