[PATCH 07/16] PM / OPP: Add multiple regulators support

From: Viresh Kumar
Date: Fri Sep 11 2015 - 08:03:31 EST


This adds support to parse multiple regulators or power-supplies in OPP
core. This doesn't use those values yet.

Signed-off-by: Viresh Kumar <viresh.kumar@xxxxxxxxxx>
---
drivers/base/power/opp/core.c | 197 +++++++++++++++++++++++++++++++--------
drivers/base/power/opp/debugfs.c | 53 +++++++----
drivers/base/power/opp/opp.h | 4 +
3 files changed, 201 insertions(+), 53 deletions(-)

diff --git a/drivers/base/power/opp/core.c b/drivers/base/power/opp/core.c
index 14e5fa10be2d..d6e945ec6467 100644
--- a/drivers/base/power/opp/core.c
+++ b/drivers/base/power/opp/core.c
@@ -502,21 +502,27 @@ static struct device_opp *_add_device_opp(struct device *dev, int supply_count)
{
struct device_opp *dev_opp;
struct device_list_opp *list_dev;
+ size_t size;

/* Check for existing list for 'dev' first */
dev_opp = _find_device_opp(dev);
if (!IS_ERR(dev_opp))
return dev_opp;

+ /* Allocate size for supply-names with dev_opp */
+ size = sizeof(*dev_opp) + supply_count * sizeof(*dev_opp->supply_names);
+
/*
* Allocate a new device OPP table. In the infrequent case where a new
* device is needed to be added, we pay this penalty.
*/
- dev_opp = kzalloc(sizeof(*dev_opp), GFP_KERNEL);
+ dev_opp = kzalloc(size, GFP_KERNEL);
if (!dev_opp)
return NULL;

dev_opp->supply_count = supply_count;
+ dev_opp->supply_names = (const char **)(dev_opp + 1);
+
INIT_LIST_HEAD(&dev_opp->dev_list);

list_dev = _add_list_dev(dev, dev_opp);
@@ -525,6 +531,13 @@ static struct device_opp *_add_device_opp(struct device *dev, int supply_count)
return NULL;
}

+ /*
+ * Initialize supply-name as dev-name for single supplies. This is
+ * required for the debugfs code.
+ */
+ if (supply_count == 1)
+ *dev_opp->supply_names = dev_name(dev);
+
srcu_init_notifier_head(&dev_opp->srcu_head);
INIT_LIST_HEAD(&dev_opp->opp_list);

@@ -682,6 +695,23 @@ _allocate_opp(struct device *dev, struct device_opp **dev_opp, int supply_count)
return opp;
}

+static bool _supplies_match(struct device_opp *dev_opp,
+ struct dev_pm_opp *old_opp,
+ struct dev_pm_opp *new_opp)
+{
+ struct opp_supply *old = old_opp->supplies;
+ struct opp_supply *new = new_opp->supplies;
+ int i;
+
+ for (i = 0; i < dev_opp->supply_count; i++)
+ if (old[i].u_volt != new[i].u_volt ||
+ old[i].u_volt_min != new[i].u_volt_min ||
+ old[i].u_volt_max != new[i].u_volt_max)
+ return false;
+
+ return true;
+}
+
static int _opp_add(struct device *dev, struct dev_pm_opp *new_opp,
struct device_opp *dev_opp)
{
@@ -712,9 +742,8 @@ static int _opp_add(struct device *dev, struct dev_pm_opp *new_opp,
opp->available, new_opp->rate,
new_opp->supplies[0].u_volt, new_opp->available);

- return opp->available &&
- opp->supplies[0].u_volt == new_opp->supplies[0].u_volt ?
- 0 : -EEXIST;
+ return opp->available && _supplies_match(dev_opp, opp, new_opp)
+ ? 0 : -EEXIST;
}

new_opp->dev_opp = dev_opp;
@@ -797,41 +826,99 @@ static int _opp_add_v1(struct device *dev, unsigned long freq, long u_volt,
return ret;
}

-/* TODO: Support multiple regulators */
-static int opp_parse_supplies(struct dev_pm_opp *opp, struct device *dev)
+/* returns the number of entries used from microvolt */
+static void opp_parse_single_supply(struct opp_supply *supply, bool triplet,
+ u32 *microvolt, u32 *microamp)
{
- struct opp_supply *supply = &opp->supplies[0];
- u32 microvolt[3] = {0};
- u32 val;
- int count, ret;
+ if (triplet) {
+ supply->u_volt = microvolt[0];
+ supply->u_volt_min = microvolt[1];
+ supply->u_volt_max = microvolt[2];
+ } else {
+ supply->u_volt = microvolt[0];
+ supply->u_volt_min = microvolt[0];
+ supply->u_volt_max = microvolt[0];
+ }
+
+ supply->u_amp = *microamp;
+}
+
+static int opp_parse_supplies(struct dev_pm_opp *opp, struct device *dev,
+ int supply_count, bool triplets)
+{
+ struct opp_supply *supply = opp->supplies;
+ int i, vcount, icount, ret, step;
+ u32 *microvolt, *microamp;

- count = of_property_count_u32_elems(opp->np, "opp-microvolt");
- if (!count)
+ vcount = of_property_count_u32_elems(opp->np, "opp-microvolt");
+ if (!vcount)
return 0;

- /* There can be one or three elements here */
- if (count != 1 && count != 3) {
- dev_err(dev, "%s: Invalid number of elements in opp-microvolt property (%d)\n",
- __func__, count);
- return -EINVAL;
- }
+ icount = of_property_count_u32_elems(opp->np, "opp-microamp");
+ if (!icount)
+ return 0;
+
+ /* Allocate memory for volt/amp */
+ microvolt = kcalloc(vcount, sizeof(*microvolt), GFP_KERNEL);
+ microamp = kcalloc(icount, sizeof(*microamp), GFP_KERNEL);
+ if (!microvolt || !microamp)
+ return -ENOMEM;

ret = of_property_read_u32_array(opp->np, "opp-microvolt", microvolt,
- count);
+ vcount);
if (ret) {
dev_err(dev, "%s: error parsing opp-microvolt: %d\n", __func__,
ret);
- return -EINVAL;
+ ret = -EINVAL;
+ goto free_microvolt;
}

- supply->u_volt = microvolt[0];
- supply->u_volt_min = microvolt[1];
- supply->u_volt_max = microvolt[2];
+ ret = of_property_read_u32_array(opp->np, "opp-microamp", microamp,
+ icount);
+ if (ret) {
+ dev_err(dev, "%s: error parsing opp-microamp: %d\n", __func__,
+ ret);
+ ret = -EINVAL;
+ goto free_microvolt;
+ }

- if (!of_property_read_u32(opp->np, "opp-microamp", &val))
- supply->u_amp = val;
+ /*
+ * For single supply, "opp-microvolt-triplets" is not mandatory and we
+ * need to find it ourselves.
+ */
+ if (supply_count == 1) {
+ if (vcount == 1) {
+ triplets = false;
+ } else if (vcount == 3) {
+ triplets = true;
+ } else {
+ dev_err(dev, "%s: opp-microvolt property should have 1 or 3 elements (%d)\n",
+ __func__, vcount);
+ ret = -EINVAL;
+ goto free_microvolt;
+ }
+ }

- return 0;
+ step = triplets ? 3 : 1;
+
+ /* microvolt sanity check */
+ if ((vcount != supply_count * step) || (icount != supply_count)) {
+ dev_err(dev, "%s: Invalid number of elements in opp-microvolt/amp property (v=%d i=%d c=%d t=%d)\n",
+ __func__, vcount, icount, supply_count * step,
+ triplets);
+ ret = -EINVAL;
+ goto free_microvolt;
+ }
+
+ for (i = 0; i < supply_count; i++)
+ opp_parse_single_supply(supply + i, triplets,
+ microvolt + step * i, microamp + i);
+
+free_microvolt:
+ kfree(microamp);
+ kfree(microvolt);
+
+ return ret;
}

/**
@@ -839,6 +926,8 @@ static int opp_parse_supplies(struct dev_pm_opp *opp, struct device *dev)
* @dev: device for which we do this operation
* @np: device node
* @supply_count: Number of supplies available for each OPP
+ * @triplets: If true, microvolt property should be in form <target min max>,
+ * else <target>.
*
* This function adds an opp definition to the opp list and returns status. The
* opp can be controlled using dev_pm_opp_enable/disable functions and may be
@@ -859,7 +948,7 @@ static int opp_parse_supplies(struct dev_pm_opp *opp, struct device *dev)
* -EINVAL Failed parsing the OPP node
*/
static int _opp_add_static_v2(struct device *dev, struct device_node *np,
- int supply_count)
+ int supply_count, bool triplets)
{
struct device_opp *dev_opp;
struct dev_pm_opp *new_opp;
@@ -898,7 +987,7 @@ static int _opp_add_static_v2(struct device *dev, struct device_node *np,
if (!of_property_read_u32(np, "clock-latency-ns", &val))
new_opp->clock_latency_ns = val;

- ret = opp_parse_supplies(new_opp, dev);
+ ret = opp_parse_supplies(new_opp, dev, supply_count, triplets);
if (ret)
goto free_opp;

@@ -1165,6 +1254,10 @@ void dev_pm_opp_of_remove_table(struct device *dev)

/* Find if dev_opp manages a single device */
if (list_is_singular(&dev_opp->dev_list)) {
+ /* Free dev_opp if no OPPs are added yet */
+ if (list_empty(&dev_opp->opp_list))
+ _remove_device_opp(dev_opp);
+
/* Free static OPPs */
list_for_each_entry_safe(opp, tmp, &dev_opp->opp_list, node) {
if (!opp->dynamic)
@@ -1197,7 +1290,9 @@ static int _of_add_opp_table_v2(struct device *dev, struct device_node *opp_np)
{
struct device_node *np;
struct device_opp *dev_opp;
- int ret = 0, count = 0;
+ const char **name;
+ int ret = 0, count, supply_count, string_count;
+ bool triplets;

dev_opp = _managed_opp(opp_np);
if (dev_opp) {
@@ -1207,12 +1302,44 @@ static int _of_add_opp_table_v2(struct device *dev, struct device_node *opp_np)
return ret;
}

+ triplets = of_property_read_bool(opp_np, "opp-microvolt-triplets");
+ string_count = of_property_count_strings(opp_np, "supply-names");
+
+ /* Fallback to single power-supply if multiple aren't present */
+ if (string_count <= 0) {
+ supply_count = 1;
+ string_count = 0;
+ } else {
+ supply_count = string_count;
+ }
+
+ /*
+ * We need to add dev_opp before adding any OPPs, so that supply_names
+ * are valid while the OPPs are getting added.
+ */
+ dev_opp = _add_device_opp(dev, supply_count);
+ if (!dev_opp)
+ return -ENOMEM;
+
+ /* Parse supply names */
+ name = dev_opp->supply_names;
+ for (count = 0; count < string_count; count++, name++) {
+ /* Parse supply names */
+ ret = of_property_read_string_index(opp_np, "supply-names",
+ count, name);
+ if (ret) {
+ dev_err(dev, "%s: read supply names (%s) error (%d)\n",
+ __func__, opp_np->name, ret);
+ goto free_table;
+ }
+ }
+
/* We have opp-list node now, iterate over it and add OPPs */
+ count = 0;
for_each_available_child_of_node(opp_np, np) {
count++;

- /* Todo: Add support for multiple supplies */
- ret = _opp_add_static_v2(dev, np, 1);
+ ret = _opp_add_static_v2(dev, np, supply_count, triplets);
if (ret) {
dev_err(dev, "%s: Failed to add OPP, %d\n", __func__,
ret);
@@ -1221,12 +1348,8 @@ static int _of_add_opp_table_v2(struct device *dev, struct device_node *opp_np)
}

/* There should be one of more OPP defined */
- if (WARN_ON(!count))
- return -ENOENT;
-
- dev_opp = _find_device_opp(dev);
- if (WARN_ON(IS_ERR(dev_opp))) {
- ret = PTR_ERR(dev_opp);
+ if (WARN_ON(!count)) {
+ ret = -ENOENT;
goto free_table;
}

diff --git a/drivers/base/power/opp/debugfs.c b/drivers/base/power/opp/debugfs.c
index e6ba29c04513..de2083d69297 100644
--- a/drivers/base/power/opp/debugfs.c
+++ b/drivers/base/power/opp/debugfs.c
@@ -31,12 +31,44 @@ void opp_debug_remove_one(struct dev_pm_opp *opp)
debugfs_remove_recursive(opp->dentry);
}

+int opp_debug_create_supplies(struct dev_pm_opp *opp,
+ struct device_opp *dev_opp, struct dentry *dentry)
+{
+ struct opp_supply *supply = opp->supplies;
+ char name[NAME_MAX];
+ const char **supply_name = dev_opp->supply_names;
+ int i;
+
+ for (i = 0; i < dev_opp->supply_count; i++, supply_name++) {
+ snprintf(name, sizeof(name), "%s_u_volt_target", *supply_name);
+ if (!debugfs_create_u32(name, S_IRUGO, dentry,
+ (u32 *)&supply->u_volt))
+ return -ENOMEM;
+
+ snprintf(name, sizeof(name), "%s_u_volt_min", *supply_name);
+ if (!debugfs_create_u32(name, S_IRUGO, dentry,
+ (u32 *)&supply->u_volt_min))
+ return -ENOMEM;
+
+ snprintf(name, sizeof(name), "%s_u_volt_max", *supply_name);
+ if (!debugfs_create_u32(name, S_IRUGO, dentry,
+ (u32 *)&supply->u_volt_max))
+ return -ENOMEM;
+ }
+
+ if (!debugfs_create_u32("u_amp", S_IRUGO, dentry,
+ (u32 *)&supply->u_amp))
+ return -ENOMEM;
+
+ return 0;
+}
+
int opp_debug_create_one(struct dev_pm_opp *opp, struct device_opp *dev_opp)
{
struct dentry *pdentry = dev_opp->dentry;
- struct opp_supply *supply = &opp->supplies[0];
struct dentry *d;
char name[15];
+ int ret;

/* Rate is unique to each OPP, use it to give opp-name */
sprintf(name, "opp:%lu", opp->rate);
@@ -59,25 +91,14 @@ int opp_debug_create_one(struct dev_pm_opp *opp, struct device_opp *dev_opp)
if (!debugfs_create_u32("rate_hz", S_IRUGO, d, (u32 *)&opp->rate))
return -ENOMEM;

- if (!debugfs_create_u32("u_volt_target", S_IRUGO, d,
- (u32 *)&supply->u_volt))
- return -ENOMEM;
-
- if (!debugfs_create_u32("u_volt_min", S_IRUGO, d,
- (u32 *)&supply->u_volt_min))
- return -ENOMEM;
-
- if (!debugfs_create_u32("u_volt_max", S_IRUGO, d,
- (u32 *)&supply->u_volt_max))
- return -ENOMEM;
-
- if (!debugfs_create_u32("u_amp", S_IRUGO, d, (u32 *)&supply->u_amp))
- return -ENOMEM;
-
if (!debugfs_create_u32("clock_latency_ns", S_IRUGO, d,
(u32 *)&opp->clock_latency_ns))
return -ENOMEM;

+ ret = opp_debug_create_supplies(opp, dev_opp, d);
+ if (ret)
+ return ret;
+
opp->dentry = d;
return 0;
}
diff --git a/drivers/base/power/opp/opp.h b/drivers/base/power/opp/opp.h
index a7a6917d6fbd..16575268f6ce 100644
--- a/drivers/base/power/opp/opp.h
+++ b/drivers/base/power/opp/opp.h
@@ -135,6 +135,7 @@ struct device_list_opp {
* @opp_list: list of opps
* @np: struct device_node pointer for opp's DT node.
* @supply_count: Number of power-supplies
+ * @supply_names: Array of strings containing names of the power-supplies
* @shared_opp: OPP is shared between multiple devices.
* @dentry: debugfs dentry pointer of the real device directory (not links).
* @dentry_name: Name of the real dentry.
@@ -157,7 +158,10 @@ struct device_opp {

struct device_node *np;
unsigned long clock_latency_ns_max;
+
unsigned int supply_count;
+ const char **supply_names;
+
bool shared_opp;
struct dev_pm_opp *suspend_opp;

--
2.4.0

--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/