[RFC PATCH 3/6] PM / Voltagedomain: introduce voltage domain driver support

Nishanth Menon
2014-02-18T20:32:20Z

Many SoCs have basic concepts of voltage rails supplying a specific
SoC device. These voltage rails may be as simple as a single regulator
or complex to be three or more regulators that are transitioned in
tandem with respect to clock changes. In some cases, they may tend
to use custom frameworks to do the actual voltage transition OR use
hardware modules that takes hints about the required configuration and
does optimized voltage transitions.

The current regulator model provides the basic building blocks for the
transitions, however SoC drivers specific to each of these devices, be
it cpufreq/devfreq have to replicate the logic for functionality.

To simply the logic, we can hence introduce a layer that takes care
of the mundane transition logic, registration mechanisms to provide
the "user drivers" such as cpufreq/devfreq a generic interface, whose
details are abstracted by the device tree description for the SoC on
which the driver operates on.

This allows us opportunity to make generic cpufreq / devfreq drivers
which may deal with a single clock, however the actual voltage change
mechanism can be customized specific to SoC without impact to the
generic driver.

Signed-off-by: Nishanth Menon <nm@ti.com>
---
drivers/power/voltdm/Kconfig | 5 +
drivers/power/voltdm/Makefile | 3 +
drivers/power/voltdm/core.c | 347 +++++++++++++++++++++++--
drivers/power/voltdm/voltage_domain_private.h | 86 ++++++
4 files changed, 424 insertions(+), 17 deletions(-)
create mode 100644 drivers/power/voltdm/voltage_domain_private.h

diff --git a/drivers/power/voltdm/Kconfig b/drivers/power/voltdm/Kconfig
index c5353bd..270fdab 100644
--- a/drivers/power/voltdm/Kconfig
+++ b/drivers/power/voltdm/Kconfig
@@ -5,3 +5,8 @@ config VOLTAGE_DOMAIN
---help---
Core voltage domain framework using common clock framework's
notifier mechanism.
+
+menu "Voltage Domain Framework Drivers"
+ depends on VOLTAGE_DOMAIN
+
+endmenu
diff --git a/drivers/power/voltdm/Makefile b/drivers/power/voltdm/Makefile
index 3fa4408..fd32040 100644
--- a/drivers/power/voltdm/Makefile
+++ b/drivers/power/voltdm/Makefile
@@ -1,2 +1,5 @@
# Generic voltage domain
obj-$(CONFIG_VOLTAGE_DOMAIN) += core.o
+
+# Hardware specific voltage domain
+# please keep this section sorted lexicographically by file/directory path name
diff --git a/drivers/power/voltdm/core.c b/drivers/power/voltdm/core.c
index d0ed27e..69f6e81 100644
--- a/drivers/power/voltdm/core.c
+++ b/drivers/power/voltdm/core.c
@@ -10,12 +10,32 @@
* that scale voltage when a clock changes its output frequency.
*/
#include <linux/device.h>
+#include <linux/module.h>
#include <linux/of.h>
#include <linux/pm_opp.h>
#include <linux/pm_voltage_domain.h>
#include <linux/regulator/consumer.h>
#include <linux/slab.h>

+#include "voltage_domain_private.h"
+
+/**
+ * struct pm_voltdm_dev - internal representation of voltage domain devices
+ * @desc: voltage domain description
+ * @dev: voltage domain device
+ * @list: list to remaining voltage domain devices
+ * @lock: mutex to control data structure modifications and serialize ops
+ * @notifier_list: list of notifiers registered for this device
+ */
+struct pm_voltdm_dev {
+ const struct pm_voltdm_desc *desc;
+ struct device *dev;
+ struct list_head list;
+ /* list lock */
+ struct mutex lock;
+ struct list_head notifier_list;
+};
+
/**
* struct volt_scale_data - Internal structure to maintain notifier information
* @dev: device on behalf of which we register the notifier
@@ -23,6 +43,9 @@
* @reg: regulator if any which is used for scaling voltage
* @tol: voltage tolerance in %
* @nb: notifier block pointer
+ * @list: list head for the notifier
+ * @vdev: pointer to voltage domain device for this notifier
+ * @voltdm_data: voltdm driver specific data
*/
struct volt_scale_data {
struct device *dev;
@@ -30,23 +53,208 @@ struct volt_scale_data {
struct regulator *reg;
int tol;
struct notifier_block nb;
+ struct list_head list;
+
+ struct pm_voltdm_dev *vdev;
+ void *voltdm_data;
};

#define to_volt_scale_data(_nb) container_of(_nb, \
struct volt_scale_data, nb)

+static DEFINE_MUTEX(pm_voltdm_list_lock);
+static LIST_HEAD(pm_voltdm_list);
+
+static inline bool voltmd_skip_check(struct pm_voltdm_dev *vdev)
+{
+ bool ret = false;
+
+ if (vdev) {
+ const struct pm_voltdm_desc *desc;
+
+ if (IS_ERR(vdev))
+ return false;
+
+ mutex_lock(&vdev->lock);
+ desc = vdev->desc;
+
+ if (desc->flags & VOLTAGE_DOMAIN_FLAG_NOTIFY_ALL)
+ ret = true;
+ mutex_unlock(&vdev->lock);
+ }
+
+ return ret;
+}
+
+static inline int voltmd_scale_voltage(struct volt_scale_data *vsd,
+ unsigned long flags, int volt, int tol)
+{
+ int ret;
+ struct pm_voltdm_dev *vdev = vsd->vdev;
+
+ if (vdev) {
+ const struct pm_voltdm_ops *ops;
+
+ if (IS_ERR(vdev))
+ return PTR_ERR(vdev);
+
+ mutex_lock(&vdev->lock);
+ ops = vdev->desc->ops;
+
+ ret = ops->voltdm_do_transition(vdev->dev,
+ vsd->voltdm_data,
+ flags, volt, tol);
+ mutex_unlock(&vdev->lock);
+ } else {
+ ret = regulator_set_voltage_tol(vsd->reg, volt, tol);
+ }
+
+ return ret;
+}
+
+static struct pm_voltdm_dev *voltdm_parse_of(struct device_node *np,
+ const char *supply,
+ struct of_phandle_args *args)
+{
+ char prop_name[32]; /* 32 is max size of property name */
+ bool found = false;
+ struct device_node *voltdm_np;
+ struct pm_voltdm_dev *vdev = NULL;
+ int ret;
+
+ snprintf(prop_name, sizeof(prop_name), "%s-voltdm", supply);
+ voltdm_np = of_parse_phandle(np, prop_name, 0);
+ if (voltdm_np) {
+ ret = of_parse_phandle_with_args(np, prop_name, "#voltdm-cells",
+ 0, args);
+ if (ret)
+ return ERR_PTR(ret);
+
+ mutex_lock(&pm_voltdm_list_lock);
+ list_for_each_entry(vdev, &pm_voltdm_list, list)
+ if (vdev->dev->parent && voltdm_np == vdev->dev->of_node) {
+ found = true;
+ break;
+ }
+ mutex_unlock(&pm_voltdm_list_lock);
+
+ /* if node is present and not ready, then defer */
+ if (!found)
+ return ERR_PTR(-EPROBE_DEFER);
+ } else {
+ return NULL;
+ }
+
+ return vdev;
+}
+
+static int voltdm_get(struct volt_scale_data *vsd, struct device_node *np,
+ const char *supply, struct of_phandle_args *args,
+ bool *skip_reg)
+{
+ struct pm_voltdm_dev *vdev = vsd->vdev;
+ struct device *dev = vsd->dev;
+ int ret = 0;
+
+ if (vdev) {
+ const struct pm_voltdm_ops *ops;
+ if (IS_ERR(vdev))
+ return PTR_ERR(vdev);
+
+ mutex_lock(&vdev->lock);
+ if (!try_module_get(vdev->dev->driver->owner)) {
+ ret = -ENODEV;
+ } else {
+ ops = vdev->desc->ops;
+ if (ops->voltdm_get)
+ ret = ops->voltdm_get(vdev->dev, dev, np,
+ args, supply,
+ &vsd->voltdm_data);
+ if (ret)
+ module_put(vdev->dev->driver->owner);
+ }
+ if (!ret)
+ list_add(&vsd->list, &vdev->notifier_list);
+
+ mutex_unlock(&vdev->lock);
+ } else {
+ vsd->reg = regulator_get_optional(dev, supply);
+ if (IS_ERR(vsd->reg))
+ ret = PTR_ERR(vsd->reg);
+ /* Regulator is not mandatory */
+ if (ret != -EPROBE_DEFER) {
+ ret = 0;
+ *skip_reg = false;
+ dev_dbg(dev, "%s: Failed to get %s regulator:%d\n",
+ __func__, supply, ret);
+ }
+ }
+
+ return ret;
+}
+
+static void voltdm_put(struct volt_scale_data *vsd)
+{
+ struct pm_voltdm_dev *vdev = vsd->vdev;
+
+ if (vdev) {
+ const struct pm_voltdm_ops *ops;
+
+ if (IS_ERR(vdev))
+ return;
+
+ mutex_lock(&vdev->lock);
+ ops = vdev->desc->ops;
+ if (ops->voltdm_put)
+ ops->voltdm_put(vdev->dev, vsd->dev, vsd->voltdm_data);
+ list_del(&vsd->list);
+ module_put(vdev->dev->driver->owner);
+ mutex_unlock(&vdev->lock);
+ } else {
+ if (!IS_ERR(vsd->reg))
+ regulator_put(vsd->reg);
+ }
+}
+
+static int voltdm_get_latency(struct volt_scale_data *vsd, int min, int max)
+{
+ struct pm_voltdm_dev *vdev = vsd->vdev;
+ const struct pm_voltdm_ops *ops;
+ int ret;
+
+ if (!vdev)
+ return regulator_set_voltage_time(vsd->reg, min, max);
+
+ if (IS_ERR(vdev))
+ return PTR_ERR(vdev);
+
+ mutex_lock(&vdev->lock);
+ ops = vdev->desc->ops;
+
+ if (!ops->voltdm_latency)
+ ret = -ENXIO;
+ else
+ ret = ops->voltdm_latency(vdev->dev, vsd->voltdm_data,
+ min, max);
+ mutex_unlock(&vdev->lock);
+
+ return ret;
+}
+
static int clk_volt_notifier_handler(struct notifier_block *nb,
unsigned long flags, void *data)
{
struct clk_notifier_data *cnd = data;
struct volt_scale_data *vsd = to_volt_scale_data(nb);
+ struct pm_voltdm_dev *vdev = vsd->vdev;
int ret, volt, tol;
struct dev_pm_opp *opp;
unsigned long old_rate = cnd->old_rate;
unsigned long new_rate = cnd->new_rate;

- if ((new_rate < old_rate && flags == PRE_RATE_CHANGE) ||
- (new_rate > old_rate && flags == POST_RATE_CHANGE))
+ if (!voltmd_skip_check(vdev) &&
+ ((new_rate < old_rate && flags == PRE_RATE_CHANGE) ||
+ (new_rate > old_rate && flags == POST_RATE_CHANGE)))
return NOTIFY_OK;

rcu_read_lock();
@@ -69,7 +277,7 @@ static int clk_volt_notifier_handler(struct notifier_block *nb,
dev_dbg(vsd->dev, "%s: %lu -> %lu, V=%d, tol=%d, clk_flag=%lu\n",
__func__, old_rate, new_rate, volt, tol, flags);

- ret = regulator_set_voltage_tol(vsd->reg, volt, tol);
+ ret = voltmd_scale_voltage(vsd, flags, volt, tol);
if (ret) {
dev_err(vsd->dev,
"%s: Failed to scale voltage(%u): %d\n", __func__,
@@ -101,6 +309,14 @@ struct notifier_block *of_pm_voltdm_notifier_register(struct device *dev,
struct dev_pm_opp *opp;
unsigned long min, max, freq;
int ret;
+ struct of_phandle_args voltdm_args = {NULL};
+ struct pm_voltdm_dev *vdev = NULL;
+ bool skip_reg = false;
+
+ /* First look for voltdm of node */
+ vdev = voltdm_parse_of(np, supply, &voltdm_args);
+ if (IS_ERR(vdev))
+ return (struct notifier_block *)vdev;

vsd = kzalloc(sizeof(*vsd), GFP_KERNEL);
if (!vsd)
@@ -109,19 +325,16 @@ struct notifier_block *of_pm_voltdm_notifier_register(struct device *dev,
vsd->dev = dev;
vsd->clk = clk;
vsd->nb.notifier_call = clk_volt_notifier_handler;
- vsd->reg = regulator_get_optional(dev, supply);
- ret = 0;
- if (IS_ERR(vsd->reg))
- ret = PTR_ERR(vsd->reg);
- /* regulator is not mandatory */
- if (ret != -EPROBE_DEFER) {
- dev_warn(dev, "%s: Failed to get %s regulator:%d\n",
+ vsd->vdev = vdev;
+ ret = voltdm_get(vsd, np, supply, &voltdm_args, &skip_reg);
+
+ if (ret) {
+ dev_warn(dev, "%s: Failed to get %s regulator/voltdm: %d\n",
__func__, supply, ret);
- ret = 0;
goto err_free_vsd;
}
- /* For devices that are not ready.... */
- if (ret)
+ /* if not mandatory... */
+ if (skip_reg)
goto err_free_vsd;

rcu_read_lock();
@@ -138,7 +351,7 @@ struct notifier_block *of_pm_voltdm_notifier_register(struct device *dev,
max = dev_pm_opp_get_voltage(opp);
rcu_read_unlock();

- *voltage_latency = regulator_set_voltage_time(vsd->reg, min, max);
+ *voltage_latency = voltdm_get_latency(vsd, min, max);
if (*voltage_latency < 0) {
dev_warn(dev,
"%s: Fail calculating voltage latency[%ld<->%ld]:%d\n",
@@ -163,7 +376,7 @@ err_bad_opp:
dev_err(dev, "%s: failed to get OPP, %d\n", __func__, ret);

err_free_reg:
- regulator_put(vsd->reg);
+ voltdm_put(vsd);

err_free_vsd:
kfree(vsd);
@@ -187,9 +400,109 @@ void of_pm_voltdm_notifier_unregister(struct notifier_block *nb)
vsd = to_volt_scale_data(nb);
clk = vsd->clk;
clk_notifier_unregister(clk, nb);
- if (!IS_ERR(vsd->reg))
- regulator_put(vsd->reg);
+ voltdm_put(vsd);

kfree(vsd);
}
EXPORT_SYMBOL_GPL(of_pm_voltdm_notifier_unregister);
+
+static void devm_voltdm_release(struct device *dev, void *res)
+{
+ struct pm_voltdm_dev *vdev = *(struct pm_voltdm_dev **)res;
+ struct volt_scale_data *vsd;
+
+ mutex_lock(&pm_voltdm_list_lock);
+ mutex_lock(&vdev->lock);
+ list_for_each_entry(vsd, &vdev->notifier_list, list) {
+ dev_warn(dev, "%s: pending notifier from device %s!\n",
+ __func__, dev_name(vsd->dev));
+ vsd->vdev = ERR_PTR(-EINVAL);
+ }
+ mutex_unlock(&vdev->lock);
+
+ list_del(&vdev->list);
+ mutex_unlock(&pm_voltdm_list_lock);
+
+ kfree(vdev);
+}
+
+/**
+ * devm_voltdm_register - Resource managed voltage domain registration
+ * @dev: pointer to the device representing the voltage domain
+ * @desc: voltage domain descriptor
+ *
+ * Called by voltage domain drivers to register a voltagedomain. Returns a
+ * valid pointer to struct pm_voltdm_dev on success or an ERR_PTR() on
+ * error. The voltagedomain will automatically be released when the device
+ * is unbound.
+ */
+struct pm_voltdm_dev *devm_voltdm_register(struct device *dev,
+ const struct pm_voltdm_desc *desc)
+{
+ struct pm_voltdm_dev **ptr, *vdev;
+
+ if (!dev || !desc)
+ return ERR_PTR(-EINVAL);
+
+ if (!desc->ops)
+ return ERR_PTR(-EINVAL);
+
+ /* Mandatory to have notify transition */
+ if (!desc->ops->voltdm_do_transition) {
+ dev_err(dev, "%s: Bad desc -do_transition missing\n", __func__);
+ return ERR_PTR(-EINVAL);
+ }
+
+ vdev = kzalloc(sizeof(*vdev), GFP_KERNEL);
+ if (!vdev)
+ return ERR_PTR(-ENOMEM);
+
+ ptr = devres_alloc(devm_voltdm_release, sizeof(*ptr), GFP_KERNEL);
+ if (!ptr) {
+ kfree(vdev);
+ return ERR_PTR(-ENOMEM);
+ }
+
+ vdev->desc = desc;
+ vdev->dev = dev;
+ mutex_init(&vdev->lock);
+ INIT_LIST_HEAD(&vdev->notifier_list);
+ mutex_lock(&pm_voltdm_list_lock);
+ list_add(&vdev->list, &pm_voltdm_list);
+ mutex_unlock(&pm_voltdm_list_lock);
+
+ *ptr = vdev;
+ devres_add(dev, ptr);
+
+ return vdev;
+}
+EXPORT_SYMBOL_GPL(devm_voltdm_register);
+
+static int devm_vdev_match(struct device *dev, void *res, void *data)
+{
+ struct pm_voltdm_dev **r = res;
+ if (!r || !*r) {
+ WARN_ON(!r || !*r);
+ return 0;
+ }
+ return *r == data;
+}
+
+/**
+ * devm_voltdm_unregister - Resource managed voltagedomain unregister
+ * @vdev: voltage domain device returned by devm_voltdm_register()
+ *
+ * Unregister a voltdm registered with devm_voltdm_register().
+ * Normally this function will not need to be called and the resource
+ * management code will ensure that the resource is freed.
+ */
+void devm_voltdm_unregister(struct pm_voltdm_dev *vdev)
+{
+ int rc;
+ struct device *dev = vdev->dev;
+
+ rc = devres_release(dev, devm_voltdm_release, devm_vdev_match, vdev);
+ if (rc != 0)
+ WARN_ON(rc);
+}
+EXPORT_SYMBOL_GPL(devm_voltdm_unregister);
diff --git a/drivers/power/voltdm/voltage_domain_private.h b/drivers/power/voltdm/voltage_domain_private.h
new file mode 100644
index 0000000..e63c2b1
--- /dev/null
+++ b/drivers/power/voltdm/voltage_domain_private.h
@@ -0,0 +1,86 @@
+/*
+ * Voltage Domain private header for voltage domain drivers
+ *
+ * Copyright (C) 2014 Texas Instruments Incorporated - http://www.ti.com/
+ * Nishanth Menon
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed "as is" WITHOUT ANY WARRANTY of any
+ * kind, whether express or implied; without even the implied warranty
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#ifndef __PM_VOLTAGE_DOMAIN_PRIVATE__
+#define __PM_VOLTAGE_DOMAIN_PRIVATE__
+
+#include <linux/pm_voltage_domain.h>
+
+struct pm_voltdm_dev;
+
+/**
+ * struct pm_voltdm_ops - Operations functions for voltage domain
+ * @voltdm_get: (optional) invoked when notifier is to be registered.
+ * @voltdm_put: (optional) invoked when notifier is unregistered.
+ * @voltdm_latency: (optional) compute and provide voltage domain
+ * transition latency.
+ * @voltdm_do_transition: (mandatory) callback for notification
+ *
+ * np_args - is arguments to the node phandle - useful when specific voltage
+ * domain is refered to using indices.
+ * voltdm_data - is the voltage domain driver specific data corresponding to
+ * driver information per registration (may point to domain driver specific
+ * data including reference to index. This is always provided back to the
+ * driver at various follow on operations.
+ * clk_notifier_flags - follows the standard clk notification flags
+ */
+struct pm_voltdm_ops {
+ int (*voltdm_get)(struct device *voltdm_dev,
+ struct device *request_dev,
+ struct device_node *np,
+ struct of_phandle_args *np_args,
+ const char *supply,
+ void **voltdm_data);
+ int (*voltdm_latency)(struct device *voltdm_dev, void *voltdm_data,
+ unsigned long min_uv, unsigned long max_uv);
+ int (*voltdm_do_transition)(struct device *voltdm_dev,
+ void *voltdm_data,
+ unsigned long clk_notifier_flags,
+ int uv, int tol_uv);
+ void (*voltdm_put)(struct device *voltdm_dev,
+ struct device *request_dev,
+ void *voltdm_data);
+};
+
+#define VOLTAGE_DOMAIN_FLAG_NOTIFY_ALL (0x1 << 0)
+
+/**
+ * struct pm_voltdm_desc - Descriptor for the voltage domain
+ * @ops: operations for the voltage domain
+ * @flags: flags controlling the various operations
+ */
+struct pm_voltdm_desc {
+ const struct pm_voltdm_ops *ops;
+ u16 flags;
+};
+
+#if defined(CONFIG_VOLTAGE_DOMAIN)
+struct pm_voltdm_dev *devm_voltdm_register(struct device *dev,
+ const struct pm_voltdm_desc *desc);
+void devm_voltdm_unregister(struct pm_voltdm_dev *vdev);
+#else
+static inline struct pm_voltdm_dev *devm_voltdm_register(struct device *dev,
+ const struct pm_voltdm_desc *desc)
+{
+ return ERR_PTR(-ENODEV);
+}
+
+static inline void devm_voltdm_unregister(struct pm_voltdm_dev *vdev)
+{
+}
+#endif /* VOLTAGE_DOMAIN */
+
+#endif /* __PM_VOLTAGE_DOMAIN_PRIVATE__ */
--
1.7.9.5