diff options
Diffstat (limited to 'drivers/opp/of.c')
| -rw-r--r-- | drivers/opp/of.c | 1300 |
1 files changed, 925 insertions, 375 deletions
diff --git a/drivers/opp/of.c b/drivers/opp/of.c index 06f0f632ec47..1e0d0adb18e1 100644 --- a/drivers/opp/of.c +++ b/drivers/opp/of.c @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0-only /* * Generic OPP OF helpers * @@ -5,10 +6,6 @@ * Nishanth Menon * Romit Dasgupta * Kevin Hilman - * - * 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. */ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt @@ -16,13 +13,17 @@ #include <linux/cpu.h> #include <linux/errno.h> #include <linux/device.h> -#include <linux/of_device.h> +#include <linux/of.h> #include <linux/pm_domain.h> #include <linux/slab.h> #include <linux/export.h> +#include <linux/energy_model.h> #include "opp.h" +/* OPP tables with uninitialized required OPPs, protected by opp_table_lock */ +static LIST_HEAD(lazy_opp_tables); + /* * Returns opp descriptor node for a device node, caller must * do of_node_put(). @@ -44,9 +45,10 @@ EXPORT_SYMBOL_GPL(dev_pm_opp_of_get_opp_desc_node); struct opp_table *_managed_opp(struct device *dev, int index) { struct opp_table *opp_table, *managed_table = NULL; - struct device_node *np; - np = _opp_of_get_opp_desc_node(dev->of_node, index); + struct device_node *np __free(device_node) = + _opp_of_get_opp_desc_node(dev->of_node, index); + if (!np) return NULL; @@ -59,17 +61,13 @@ struct opp_table *_managed_opp(struct device *dev, int index) * But the OPPs will be considered as shared only if the * OPP table contains a "opp-shared" property. */ - if (opp_table->shared_opp == OPP_TABLE_ACCESS_SHARED) { - _get_opp_table_kref(opp_table); - managed_table = opp_table; - } + if (opp_table->shared_opp == OPP_TABLE_ACCESS_SHARED) + managed_table = dev_pm_opp_get_opp_table_ref(opp_table); break; } } - of_node_put(np); - return managed_table; } @@ -79,60 +77,40 @@ static struct dev_pm_opp *_find_opp_of_np(struct opp_table *opp_table, { struct dev_pm_opp *opp; - lockdep_assert_held(&opp_table_lock); - - mutex_lock(&opp_table->lock); + guard(mutex)(&opp_table->lock); list_for_each_entry(opp, &opp_table->opp_list, node) { - if (opp->np == opp_np) { - dev_pm_opp_get(opp); - mutex_unlock(&opp_table->lock); - return opp; - } + if (opp->np == opp_np) + return dev_pm_opp_get(opp); } - mutex_unlock(&opp_table->lock); - return NULL; } static struct device_node *of_parse_required_opp(struct device_node *np, int index) { - struct device_node *required_np; - - required_np = of_parse_phandle(np, "required-opps", index); - if (unlikely(!required_np)) { - pr_err("%s: Unable to parse required-opps: %pOF, index: %d\n", - __func__, np, index); - } - - return required_np; + return of_parse_phandle(np, "required-opps", index); } /* The caller must call dev_pm_opp_put_opp_table() after the table is used */ static struct opp_table *_find_table_of_opp_np(struct device_node *opp_np) { struct opp_table *opp_table; - struct device_node *opp_table_np; - lockdep_assert_held(&opp_table_lock); + struct device_node *opp_table_np __free(device_node) = + of_get_parent(opp_np); - opp_table_np = of_get_parent(opp_np); if (!opp_table_np) - goto err; + return ERR_PTR(-ENODEV); - /* It is safe to put the node now as all we need now is its address */ - of_node_put(opp_table_np); + guard(mutex)(&opp_table_lock); list_for_each_entry(opp_table, &opp_tables, node) { - if (opp_table_np == opp_table->np) { - _get_opp_table_kref(opp_table); - return opp_table; - } + if (opp_table_np == opp_table->np) + return dev_pm_opp_get_opp_table_ref(opp_table); } -err: return ERR_PTR(-ENODEV); } @@ -140,7 +118,6 @@ err: static void _opp_table_free_required_tables(struct opp_table *opp_table) { struct opp_table **required_opp_tables = opp_table->required_opp_tables; - struct device **genpd_virt_devs = opp_table->genpd_virt_devs; int i; if (!required_opp_tables) @@ -148,17 +125,18 @@ static void _opp_table_free_required_tables(struct opp_table *opp_table) for (i = 0; i < opp_table->required_opp_count; i++) { if (IS_ERR_OR_NULL(required_opp_tables[i])) - break; + continue; dev_pm_opp_put_opp_table(required_opp_tables[i]); } kfree(required_opp_tables); - kfree(genpd_virt_devs); opp_table->required_opp_count = 0; - opp_table->genpd_virt_devs = NULL; opp_table->required_opp_tables = NULL; + + guard(mutex)(&opp_table_lock); + list_del(&opp_table->lazy); } /* @@ -170,81 +148,69 @@ static void _opp_table_alloc_required_tables(struct opp_table *opp_table, struct device_node *opp_np) { struct opp_table **required_opp_tables; - struct device **genpd_virt_devs = NULL; - struct device_node *required_np, *np; - int count, i; + bool lazy = false; + int count, i, size; /* Traversing the first OPP node is all we need */ - np = of_get_next_available_child(opp_np, NULL); + struct device_node *np __free(device_node) = + of_get_next_available_child(opp_np, NULL); + if (!np) { - dev_err(dev, "Empty OPP table\n"); + dev_warn(dev, "Empty OPP table\n"); return; } count = of_count_phandle_with_args(np, "required-opps", NULL); - if (!count) - goto put_np; - - if (count > 1) { - genpd_virt_devs = kcalloc(count, sizeof(*genpd_virt_devs), - GFP_KERNEL); - if (!genpd_virt_devs) - goto put_np; - } + if (count <= 0) + return; - required_opp_tables = kcalloc(count, sizeof(*required_opp_tables), - GFP_KERNEL); - if (!required_opp_tables) { - kfree(genpd_virt_devs); - goto put_np; - } + size = sizeof(*required_opp_tables) + sizeof(*opp_table->required_devs); + required_opp_tables = kcalloc(count, size, GFP_KERNEL); + if (!required_opp_tables) + return; - opp_table->genpd_virt_devs = genpd_virt_devs; opp_table->required_opp_tables = required_opp_tables; + opp_table->required_devs = (void *)(required_opp_tables + count); opp_table->required_opp_count = count; for (i = 0; i < count; i++) { - required_np = of_parse_required_opp(np, i); - if (!required_np) - goto free_required_tables; + struct device_node *required_np __free(device_node) = + of_parse_required_opp(np, i); + + if (!required_np) { + _opp_table_free_required_tables(opp_table); + return; + } required_opp_tables[i] = _find_table_of_opp_np(required_np); - of_node_put(required_np); if (IS_ERR(required_opp_tables[i])) - goto free_required_tables; + lazy = true; + } + /* Let's do the linking later on */ + if (lazy) { /* - * We only support genpd's OPPs in the "required-opps" for now, - * as we don't know how much about other cases. Error out if the - * required OPP doesn't belong to a genpd. + * The OPP table is not held while allocating the table, take it + * now to avoid corruption to the lazy_opp_tables list. */ - if (!required_opp_tables[i]->is_genpd) { - dev_err(dev, "required-opp doesn't belong to genpd: %pOF\n", - required_np); - goto free_required_tables; - } + guard(mutex)(&opp_table_lock); + list_add(&opp_table->lazy, &lazy_opp_tables); } - - goto put_np; - -free_required_tables: - _opp_table_free_required_tables(opp_table); -put_np: - of_node_put(np); } void _of_init_opp_table(struct opp_table *opp_table, struct device *dev, int index) { - struct device_node *np, *opp_np; + struct device_node *opp_np; u32 val; /* * Only required for backward compatibility with v1 bindings, but isn't * harmful for other cases. And so we do it unconditionally. */ - np = of_node_get(dev->of_node); + struct device_node *np __free(device_node) = of_node_get(dev->of_node); + if (!np) return; @@ -253,13 +219,11 @@ void _of_init_opp_table(struct opp_table *opp_table, struct device *dev, of_property_read_u32(np, "voltage-tolerance", &opp_table->voltage_tolerance_v1); - if (of_find_property(np, "#power-domain-cells", NULL)) + if (of_property_present(np, "#power-domain-cells")) opp_table->is_genpd = true; /* Get OPP table node */ opp_np = _opp_of_get_opp_desc_node(np, index); - of_node_put(np); - if (!opp_np) return; @@ -271,20 +235,20 @@ void _of_init_opp_table(struct opp_table *opp_table, struct device *dev, opp_table->np = opp_np; _opp_table_alloc_required_tables(opp_table, dev, opp_np); - of_node_put(opp_np); } void _of_clear_opp_table(struct opp_table *opp_table) { _opp_table_free_required_tables(opp_table); + of_node_put(opp_table->np); } /* * Release all resources previously acquired with a call to * _of_opp_alloc_required_opps(). */ -void _of_opp_free_required_opps(struct opp_table *opp_table, - struct dev_pm_opp *opp) +static void _of_opp_free_required_opps(struct opp_table *opp_table, + struct dev_pm_opp *opp) { struct dev_pm_opp **required_opps = opp->required_opps; int i; @@ -294,52 +258,65 @@ void _of_opp_free_required_opps(struct opp_table *opp_table, for (i = 0; i < opp_table->required_opp_count; i++) { if (!required_opps[i]) - break; + continue; /* Put the reference back */ dev_pm_opp_put(required_opps[i]); } - kfree(required_opps); opp->required_opps = NULL; + kfree(required_opps); +} + +void _of_clear_opp(struct opp_table *opp_table, struct dev_pm_opp *opp) +{ + _of_opp_free_required_opps(opp_table, opp); + of_node_put(opp->np); +} + +static int _link_required_opps(struct dev_pm_opp *opp, + struct opp_table *required_table, int index) +{ + struct device_node *np __free(device_node) = + of_parse_required_opp(opp->np, index); + + if (unlikely(!np)) + return -ENODEV; + + opp->required_opps[index] = _find_opp_of_np(required_table, np); + if (!opp->required_opps[index]) { + pr_err("%s: Unable to find required OPP node: %pOF (%d)\n", + __func__, opp->np, index); + return -ENODEV; + } + + return 0; } /* Populate all required OPPs which are part of "required-opps" list */ static int _of_opp_alloc_required_opps(struct opp_table *opp_table, struct dev_pm_opp *opp) { - struct dev_pm_opp **required_opps; struct opp_table *required_table; - struct device_node *np; int i, ret, count = opp_table->required_opp_count; if (!count) return 0; - required_opps = kcalloc(count, sizeof(*required_opps), GFP_KERNEL); - if (!required_opps) + opp->required_opps = kcalloc(count, sizeof(*opp->required_opps), GFP_KERNEL); + if (!opp->required_opps) return -ENOMEM; - opp->required_opps = required_opps; - for (i = 0; i < count; i++) { required_table = opp_table->required_opp_tables[i]; - np = of_parse_required_opp(opp->np, i); - if (unlikely(!np)) { - ret = -ENODEV; - goto free_required_opps; - } - - required_opps[i] = _find_opp_of_np(required_table, np); - of_node_put(np); + /* Required table not added yet, we will link later */ + if (IS_ERR_OR_NULL(required_table)) + continue; - if (!required_opps[i]) { - pr_err("%s: Unable to find required OPP node: %pOF (%d)\n", - __func__, opp->np, i); - ret = -ENODEV; + ret = _link_required_opps(opp, required_table, i); + if (ret) goto free_required_opps; - } } return 0; @@ -350,12 +327,182 @@ free_required_opps: return ret; } +/* Link required OPPs for an individual OPP */ +static int lazy_link_required_opps(struct opp_table *opp_table, + struct opp_table *new_table, int index) +{ + struct dev_pm_opp *opp; + int ret; + + list_for_each_entry(opp, &opp_table->opp_list, node) { + ret = _link_required_opps(opp, new_table, index); + if (ret) + return ret; + } + + return 0; +} + +/* Link required OPPs for all OPPs of the newly added OPP table */ +static void lazy_link_required_opp_table(struct opp_table *new_table) +{ + struct opp_table *opp_table, *temp, **required_opp_tables; + struct dev_pm_opp *opp; + int i, ret; + + guard(mutex)(&opp_table_lock); + + list_for_each_entry_safe(opp_table, temp, &lazy_opp_tables, lazy) { + bool lazy = false; + + /* opp_np can't be invalid here */ + struct device_node *opp_np __free(device_node) = + of_get_next_available_child(opp_table->np, NULL); + + for (i = 0; i < opp_table->required_opp_count; i++) { + required_opp_tables = opp_table->required_opp_tables; + + /* Required opp-table is already parsed */ + if (!IS_ERR(required_opp_tables[i])) + continue; + + /* required_np can't be invalid here */ + struct device_node *required_np __free(device_node) = + of_parse_required_opp(opp_np, i); + struct device_node *required_table_np __free(device_node) = + of_get_parent(required_np); + + /* + * Newly added table isn't the required opp-table for + * opp_table. + */ + if (required_table_np != new_table->np) { + lazy = true; + continue; + } + + required_opp_tables[i] = dev_pm_opp_get_opp_table_ref(new_table); + + /* Link OPPs now */ + ret = lazy_link_required_opps(opp_table, new_table, i); + if (ret) { + /* The OPPs will be marked unusable */ + lazy = false; + break; + } + } + + /* All required opp-tables found, remove from lazy list */ + if (!lazy) { + list_del_init(&opp_table->lazy); + + list_for_each_entry(opp, &opp_table->opp_list, node) + _required_opps_available(opp, opp_table->required_opp_count); + } + } +} + +static int _bandwidth_supported(struct device *dev, struct opp_table *opp_table) +{ + struct device_node *opp_np __free(device_node) = NULL; + struct property *prop; + + if (!opp_table) { + struct device_node *np __free(device_node) = + of_node_get(dev->of_node); + + if (!np) + return -ENODEV; + + opp_np = _opp_of_get_opp_desc_node(np, 0); + } else { + opp_np = of_node_get(opp_table->np); + } + + /* Lets not fail in case we are parsing opp-v1 bindings */ + if (!opp_np) + return 0; + + /* Checking only first OPP is sufficient */ + struct device_node *np __free(device_node) = + of_get_next_available_child(opp_np, NULL); + + if (!np) { + dev_err(dev, "OPP table empty\n"); + return -EINVAL; + } + + prop = of_find_property(np, "opp-peak-kBps", NULL); + if (!prop || !prop->length) + return 0; + + return 1; +} + +int dev_pm_opp_of_find_icc_paths(struct device *dev, + struct opp_table *opp_table) +{ + struct device_node *np __free(device_node) = of_node_get(dev->of_node); + int ret, i, count, num_paths; + struct icc_path **paths; + + ret = _bandwidth_supported(dev, opp_table); + if (ret == -EINVAL) + return 0; /* Empty OPP table is a valid corner-case, let's not fail */ + else if (ret <= 0) + return ret; + + if (!np) + return 0; + + ret = 0; + + count = of_count_phandle_with_args(np, "interconnects", + "#interconnect-cells"); + if (count < 0) + return 0; + + /* two phandles when #interconnect-cells = <1> */ + if (count % 2) { + dev_err(dev, "%s: Invalid interconnects values\n", __func__); + return -EINVAL; + } + + num_paths = count / 2; + paths = kcalloc(num_paths, sizeof(*paths), GFP_KERNEL); + if (!paths) + return -ENOMEM; + + for (i = 0; i < num_paths; i++) { + paths[i] = of_icc_get_by_index(dev, i); + if (IS_ERR(paths[i])) { + ret = dev_err_probe(dev, PTR_ERR(paths[i]), "%s: Unable to get path%d\n", __func__, i); + goto err; + } + } + + if (opp_table) { + opp_table->paths = paths; + opp_table->path_count = num_paths; + return 0; + } + +err: + while (i--) + icc_put(paths[i]); + + kfree(paths); + + return ret; +} +EXPORT_SYMBOL_GPL(dev_pm_opp_of_find_icc_paths); + static bool _opp_is_supported(struct device *dev, struct opp_table *opp_table, struct device_node *np) { - unsigned int count = opp_table->supported_hw_count; - u32 version; - int ret; + unsigned int levels = opp_table->supported_hw_count; + int count, versions, ret, i, j; + u32 val; if (!opp_table->supported_hw) { /* @@ -364,160 +511,192 @@ static bool _opp_is_supported(struct device *dev, struct opp_table *opp_table, * an OPP then the OPP should not be enabled as there is * no way to see if the hardware supports it. */ - if (of_find_property(np, "opp-supported-hw", NULL)) + if (of_property_present(np, "opp-supported-hw")) return false; else return true; } - while (count--) { - ret = of_property_read_u32_index(np, "opp-supported-hw", count, - &version); - if (ret) { - dev_warn(dev, "%s: failed to read opp-supported-hw property at index %d: %d\n", - __func__, count, ret); - return false; + count = of_property_count_u32_elems(np, "opp-supported-hw"); + if (count <= 0 || count % levels) { + dev_err(dev, "%s: Invalid opp-supported-hw property (%d)\n", + __func__, count); + return false; + } + + versions = count / levels; + + /* All levels in at least one of the versions should match */ + for (i = 0; i < versions; i++) { + bool supported = true; + + for (j = 0; j < levels; j++) { + ret = of_property_read_u32_index(np, "opp-supported-hw", + i * levels + j, &val); + if (ret) { + dev_warn(dev, "%s: failed to read opp-supported-hw property at index %d: %d\n", + __func__, i * levels + j, ret); + return false; + } + + /* Check if the level is supported */ + if (!(val & opp_table->supported_hw[j])) { + supported = false; + break; + } } - /* Both of these are bitwise masks of the versions */ - if (!(version & opp_table->supported_hw[count])) - return false; + if (supported) + return true; } - return true; + return false; } -static int opp_parse_supplies(struct dev_pm_opp *opp, struct device *dev, - struct opp_table *opp_table) +static u32 *_parse_named_prop(struct dev_pm_opp *opp, struct device *dev, + struct opp_table *opp_table, + const char *prop_type, bool *triplet) { - u32 *microvolt, *microamp = NULL; - int supplies = opp_table->regulator_count, vcount, icount, ret, i, j; struct property *prop = NULL; char name[NAME_MAX]; + int count, ret; + u32 *out; - /* Search for "opp-microvolt-<name>" */ + /* Search for "opp-<prop_type>-<name>" */ if (opp_table->prop_name) { - snprintf(name, sizeof(name), "opp-microvolt-%s", + snprintf(name, sizeof(name), "opp-%s-%s", prop_type, opp_table->prop_name); prop = of_find_property(opp->np, name, NULL); } if (!prop) { - /* Search for "opp-microvolt" */ - sprintf(name, "opp-microvolt"); + /* Search for "opp-<prop_type>" */ + snprintf(name, sizeof(name), "opp-%s", prop_type); prop = of_find_property(opp->np, name, NULL); - - /* Missing property isn't a problem, but an invalid entry is */ - if (!prop) { - if (unlikely(supplies == -1)) { - /* Initialize regulator_count */ - opp_table->regulator_count = 0; - return 0; - } - - if (!supplies) - return 0; - - dev_err(dev, "%s: opp-microvolt missing although OPP managing regulators\n", - __func__); - return -EINVAL; - } + if (!prop) + return NULL; } - if (unlikely(supplies == -1)) { - /* Initialize regulator_count */ - supplies = opp_table->regulator_count = 1; - } else if (unlikely(!supplies)) { - dev_err(dev, "%s: opp-microvolt wasn't expected\n", __func__); - return -EINVAL; + count = of_property_count_u32_elems(opp->np, name); + if (count < 0) { + dev_err(dev, "%s: Invalid %s property (%d)\n", __func__, name, + count); + return ERR_PTR(count); } - vcount = of_property_count_u32_elems(opp->np, name); - if (vcount < 0) { - dev_err(dev, "%s: Invalid %s property (%d)\n", - __func__, name, vcount); - return vcount; + /* + * Initialize regulator_count, if regulator information isn't provided + * by the platform. Now that one of the properties is available, fix the + * regulator_count to 1. + */ + if (unlikely(opp_table->regulator_count == -1)) + opp_table->regulator_count = 1; + + if (count != opp_table->regulator_count && + (!triplet || count != opp_table->regulator_count * 3)) { + dev_err(dev, "%s: Invalid number of elements in %s property (%u) with supplies (%d)\n", + __func__, prop_type, count, opp_table->regulator_count); + return ERR_PTR(-EINVAL); } - /* There can be one or three elements per supply */ - if (vcount != supplies && vcount != supplies * 3) { - dev_err(dev, "%s: Invalid number of elements in %s property (%d) with supplies (%d)\n", - __func__, name, vcount, supplies); - return -EINVAL; - } + out = kmalloc_array(count, sizeof(*out), GFP_KERNEL); + if (!out) + return ERR_PTR(-EINVAL); - microvolt = kmalloc_array(vcount, sizeof(*microvolt), GFP_KERNEL); - if (!microvolt) - return -ENOMEM; - - ret = of_property_read_u32_array(opp->np, name, microvolt, vcount); + ret = of_property_read_u32_array(opp->np, name, out, count); if (ret) { dev_err(dev, "%s: error parsing %s: %d\n", __func__, name, ret); - ret = -EINVAL; - goto free_microvolt; + kfree(out); + return ERR_PTR(-EINVAL); } - /* Search for "opp-microamp-<name>" */ - prop = NULL; - if (opp_table->prop_name) { - snprintf(name, sizeof(name), "opp-microamp-%s", - opp_table->prop_name); - prop = of_find_property(opp->np, name, NULL); - } + if (triplet) + *triplet = count != opp_table->regulator_count; - if (!prop) { - /* Search for "opp-microamp" */ - sprintf(name, "opp-microamp"); - prop = of_find_property(opp->np, name, NULL); - } + return out; +} - if (prop) { - icount = of_property_count_u32_elems(opp->np, name); - if (icount < 0) { - dev_err(dev, "%s: Invalid %s property (%d)\n", __func__, - name, icount); - ret = icount; - goto free_microvolt; - } +static u32 *opp_parse_microvolt(struct dev_pm_opp *opp, struct device *dev, + struct opp_table *opp_table, bool *triplet) +{ + u32 *microvolt; - if (icount != supplies) { - dev_err(dev, "%s: Invalid number of elements in %s property (%d) with supplies (%d)\n", - __func__, name, icount, supplies); - ret = -EINVAL; - goto free_microvolt; - } + microvolt = _parse_named_prop(opp, dev, opp_table, "microvolt", triplet); + if (IS_ERR(microvolt)) + return microvolt; - microamp = kmalloc_array(icount, sizeof(*microamp), GFP_KERNEL); - if (!microamp) { - ret = -EINVAL; - goto free_microvolt; + if (!microvolt) { + /* + * Missing property isn't a problem, but an invalid + * entry is. This property isn't optional if regulator + * information is provided. Check only for the first OPP, as + * regulator_count may get initialized after that to a valid + * value. + */ + if (list_empty(&opp_table->opp_list) && + opp_table->regulator_count > 0) { + dev_err(dev, "%s: opp-microvolt missing although OPP managing regulators\n", + __func__); + return ERR_PTR(-EINVAL); } + } - ret = of_property_read_u32_array(opp->np, name, microamp, - icount); - if (ret) { - dev_err(dev, "%s: error parsing %s: %d\n", __func__, - name, ret); - ret = -EINVAL; - goto free_microamp; - } + return microvolt; +} + +static int opp_parse_supplies(struct dev_pm_opp *opp, struct device *dev, + struct opp_table *opp_table) +{ + u32 *microvolt, *microamp, *microwatt; + int ret = 0, i, j; + bool triplet; + + microvolt = opp_parse_microvolt(opp, dev, opp_table, &triplet); + if (IS_ERR(microvolt)) + return PTR_ERR(microvolt); + + microamp = _parse_named_prop(opp, dev, opp_table, "microamp", NULL); + if (IS_ERR(microamp)) { + ret = PTR_ERR(microamp); + goto free_microvolt; } - for (i = 0, j = 0; i < supplies; i++) { - opp->supplies[i].u_volt = microvolt[j++]; + microwatt = _parse_named_prop(opp, dev, opp_table, "microwatt", NULL); + if (IS_ERR(microwatt)) { + ret = PTR_ERR(microwatt); + goto free_microamp; + } - if (vcount == supplies) { - opp->supplies[i].u_volt_min = opp->supplies[i].u_volt; - opp->supplies[i].u_volt_max = opp->supplies[i].u_volt; - } else { - opp->supplies[i].u_volt_min = microvolt[j++]; - opp->supplies[i].u_volt_max = microvolt[j++]; + /* + * Initialize regulator_count if it is uninitialized and no properties + * are found. + */ + if (unlikely(opp_table->regulator_count == -1)) { + opp_table->regulator_count = 0; + return 0; + } + + for (i = 0, j = 0; i < opp_table->regulator_count; i++) { + if (microvolt) { + opp->supplies[i].u_volt = microvolt[j++]; + + if (triplet) { + opp->supplies[i].u_volt_min = microvolt[j++]; + opp->supplies[i].u_volt_max = microvolt[j++]; + } else { + opp->supplies[i].u_volt_min = opp->supplies[i].u_volt; + opp->supplies[i].u_volt_max = opp->supplies[i].u_volt; + } } if (microamp) opp->supplies[i].u_amp = microamp[i]; + + if (microwatt) + opp->supplies[i].u_watt = microwatt[i]; } + kfree(microwatt); free_microamp: kfree(microamp); free_microvolt: @@ -535,10 +714,131 @@ free_microvolt: */ void dev_pm_opp_of_remove_table(struct device *dev) { - _dev_pm_opp_find_and_remove_table(dev); + dev_pm_opp_remove_table(dev); } EXPORT_SYMBOL_GPL(dev_pm_opp_of_remove_table); +static int _read_rate(struct dev_pm_opp *new_opp, struct opp_table *opp_table, + struct device_node *np) +{ + struct property *prop; + int i, count, ret; + u64 *rates; + + prop = of_find_property(np, "opp-hz", NULL); + if (!prop) + return -ENODEV; + + count = prop->length / sizeof(u64); + if (opp_table->clk_count != count) { + pr_err("%s: Count mismatch between opp-hz and clk_count (%d %d)\n", + __func__, count, opp_table->clk_count); + return -EINVAL; + } + + rates = kmalloc_array(count, sizeof(*rates), GFP_KERNEL); + if (!rates) + return -ENOMEM; + + ret = of_property_read_u64_array(np, "opp-hz", rates, count); + if (ret) { + pr_err("%s: Error parsing opp-hz: %d\n", __func__, ret); + } else { + /* + * Rate is defined as an unsigned long in clk API, and so + * casting explicitly to its type. Must be fixed once rate is 64 + * bit guaranteed in clk API. + */ + for (i = 0; i < count; i++) { + new_opp->rates[i] = (unsigned long)rates[i]; + + /* This will happen for frequencies > 4.29 GHz */ + WARN_ON(new_opp->rates[i] != rates[i]); + } + } + + kfree(rates); + + return ret; +} + +static int _read_bw(struct dev_pm_opp *new_opp, struct opp_table *opp_table, + struct device_node *np, bool peak) +{ + const char *name = peak ? "opp-peak-kBps" : "opp-avg-kBps"; + struct property *prop; + int i, count, ret; + u32 *bw; + + prop = of_find_property(np, name, NULL); + if (!prop) + return -ENODEV; + + count = prop->length / sizeof(u32); + if (opp_table->path_count != count) { + pr_err("%s: Mismatch between %s and paths (%d %d)\n", + __func__, name, count, opp_table->path_count); + return -EINVAL; + } + + bw = kmalloc_array(count, sizeof(*bw), GFP_KERNEL); + if (!bw) + return -ENOMEM; + + ret = of_property_read_u32_array(np, name, bw, count); + if (ret) { + pr_err("%s: Error parsing %s: %d\n", __func__, name, ret); + goto out; + } + + for (i = 0; i < count; i++) { + if (peak) + new_opp->bandwidth[i].peak = kBps_to_icc(bw[i]); + else + new_opp->bandwidth[i].avg = kBps_to_icc(bw[i]); + } + +out: + kfree(bw); + return ret; +} + +static int _read_opp_key(struct dev_pm_opp *new_opp, + struct opp_table *opp_table, struct device_node *np) +{ + bool found = false; + int ret; + + ret = _read_rate(new_opp, opp_table, np); + if (!ret) + found = true; + else if (ret != -ENODEV) + return ret; + + /* + * Bandwidth consists of peak and average (optional) values: + * opp-peak-kBps = <path1_value path2_value>; + * opp-avg-kBps = <path1_value path2_value>; + */ + ret = _read_bw(new_opp, opp_table, np, true); + if (!ret) { + found = true; + ret = _read_bw(new_opp, opp_table, np, false); + } + + /* The properties were found but we failed to parse them */ + if (ret && ret != -ENODEV) + return ret; + + if (!of_property_read_u32(np, "opp-level", &new_opp->level)) + found = true; + + if (found) + return 0; + + return ret; +} + /** * _opp_add_static_v2() - Allocate static OPPs (As per 'v2' DT bindings) * @opp_table: OPP table @@ -567,48 +867,35 @@ static struct dev_pm_opp *_opp_add_static_v2(struct opp_table *opp_table, struct device *dev, struct device_node *np) { struct dev_pm_opp *new_opp; - u64 rate = 0; u32 val; int ret; - bool rate_not_available = false; new_opp = _opp_allocate(opp_table); if (!new_opp) return ERR_PTR(-ENOMEM); - ret = of_property_read_u64(np, "opp-hz", &rate); + ret = _read_opp_key(new_opp, opp_table, np); if (ret < 0) { - /* "opp-hz" is optional for devices like power domains. */ - if (!opp_table->is_genpd) { - dev_err(dev, "%s: opp-hz not found\n", __func__); - goto free_opp; - } - - rate_not_available = true; - } else { - /* - * Rate is defined as an unsigned long in clk API, and so - * casting explicitly to its type. Must be fixed once rate is 64 - * bit guaranteed in clk API. - */ - new_opp->rate = (unsigned long)rate; + dev_err(dev, "%s: opp key field not found\n", __func__); + goto free_opp; } /* Check if the OPP supports hardware's hierarchy of versions or not */ if (!_opp_is_supported(dev, opp_table, np)) { - dev_dbg(dev, "OPP not supported by hardware: %llu\n", rate); + dev_dbg(dev, "OPP not supported by hardware: %s\n", + of_node_full_name(np)); goto free_opp; } new_opp->turbo = of_property_read_bool(np, "turbo-mode"); - new_opp->np = np; + new_opp->np = of_node_get(np); new_opp->dynamic = false; new_opp->available = true; ret = _of_opp_alloc_required_opps(opp_table, new_opp); if (ret) - goto free_opp; + goto put_node; if (!of_property_read_u32(np, "clock-latency-ns", &val)) new_opp->clock_latency_ns = val; @@ -617,10 +904,7 @@ static struct dev_pm_opp *_opp_add_static_v2(struct opp_table *opp_table, if (ret) goto free_required_opps; - if (opp_table->is_genpd) - new_opp->pstate = pm_genpd_opp_to_performance_state(dev, new_opp); - - ret = _opp_add(dev, new_opp, opp_table, rate_not_available); + ret = _opp_add(dev, new_opp, opp_table); if (ret) { /* Don't return error for duplicate OPPs */ if (ret == -EBUSY) @@ -631,9 +915,12 @@ static struct dev_pm_opp *_opp_add_static_v2(struct opp_table *opp_table, /* OPP to select on device suspend */ if (of_property_read_bool(np, "opp-suspend")) { if (opp_table->suspend_opp) { - dev_warn(dev, "%s: Multiple suspend OPPs found (%lu %lu)\n", - __func__, opp_table->suspend_opp->rate, - new_opp->rate); + /* Pick the OPP with higher rate/bw/level as suspend OPP */ + if (_opp_compare_key(opp_table, new_opp, opp_table->suspend_opp) == 1) { + opp_table->suspend_opp->suspend = false; + new_opp->suspend = true; + opp_table->suspend_opp = new_opp; + } } else { new_opp->suspend = true; opp_table->suspend_opp = new_opp; @@ -643,10 +930,11 @@ static struct dev_pm_opp *_opp_add_static_v2(struct opp_table *opp_table, if (new_opp->clock_latency_ns > opp_table->clock_latency_ns_max) opp_table->clock_latency_ns_max = new_opp->clock_latency_ns; - pr_debug("%s: turbo:%d rate:%lu uv:%lu uvmin:%lu uvmax:%lu latency:%lu\n", - __func__, new_opp->turbo, new_opp->rate, + pr_debug("%s: turbo:%d rate:%lu uv:%lu uvmin:%lu uvmax:%lu latency:%lu level:%u\n", + __func__, new_opp->turbo, new_opp->rates[0], new_opp->supplies[0].u_volt, new_opp->supplies[0].u_volt_min, - new_opp->supplies[0].u_volt_max, new_opp->clock_latency_ns); + new_opp->supplies[0].u_volt_max, new_opp->clock_latency_ns, + new_opp->level); /* * Notify the changes in the availability of the operable @@ -657,26 +945,30 @@ static struct dev_pm_opp *_opp_add_static_v2(struct opp_table *opp_table, free_required_opps: _of_opp_free_required_opps(opp_table, new_opp); +put_node: + of_node_put(np); free_opp: _opp_free(new_opp); - return ERR_PTR(ret); + return ret ? ERR_PTR(ret) : NULL; } /* Initializes OPP tables based on new bindings */ static int _of_add_opp_table_v2(struct device *dev, struct opp_table *opp_table) { struct device_node *np; - int ret, count = 0, pstate_count = 0; + int ret, count = 0; struct dev_pm_opp *opp; /* OPP table is already initialized for the device */ - if (opp_table->parsed_static_opps) { - kref_get(&opp_table->list_kref); - return 0; - } + scoped_guard(mutex, &opp_table->lock) { + if (opp_table->parsed_static_opps) { + opp_table->parsed_static_opps++; + return 0; + } - kref_init(&opp_table->list_kref); + opp_table->parsed_static_opps = 1; + } /* We have opp-table node now, iterate over it and add OPPs */ for_each_available_child_of_node(opp_table->np, np) { @@ -686,38 +978,25 @@ static int _of_add_opp_table_v2(struct device *dev, struct opp_table *opp_table) dev_err(dev, "%s: Failed to add OPP, %d\n", __func__, ret); of_node_put(np); - goto put_list_kref; + goto remove_static_opp; } else if (opp) { count++; } } - /* There should be one of more OPP defined */ - if (WARN_ON(!count)) { - ret = -ENOENT; - goto put_list_kref; - } - - list_for_each_entry(opp, &opp_table->opp_list, node) - pstate_count += !!opp->pstate; - - /* Either all or none of the nodes shall have performance state set */ - if (pstate_count && pstate_count != count) { - dev_err(dev, "Not all nodes have performance state set (%d: %d)\n", - count, pstate_count); + /* There should be one or more OPPs defined */ + if (!count) { + dev_err(dev, "%s: no supported OPPs", __func__); ret = -ENOENT; - goto put_list_kref; + goto remove_static_opp; } - if (pstate_count) - opp_table->genpd_performance_state = true; - - opp_table->parsed_static_opps = true; + lazy_link_required_opp_table(opp_table); return 0; -put_list_kref: - _put_opp_list_kref(opp_table); +remove_static_opp: + _opp_remove_all_static(opp_table); return ret; } @@ -729,11 +1008,24 @@ static int _of_add_opp_table_v1(struct device *dev, struct opp_table *opp_table) const __be32 *val; int nr, ret = 0; + scoped_guard(mutex, &opp_table->lock) { + if (opp_table->parsed_static_opps) { + opp_table->parsed_static_opps++; + return 0; + } + + opp_table->parsed_static_opps = 1; + } + prop = of_find_property(dev->of_node, "operating-points", NULL); - if (!prop) - return -ENODEV; - if (!prop->value) - return -ENODATA; + if (!prop) { + ret = -ENODEV; + goto remove_static_opp; + } + if (!prop->value) { + ret = -ENODATA; + goto remove_static_opp; + } /* * Each OPP is a set of tuples consisting of frequency and @@ -742,54 +1034,55 @@ static int _of_add_opp_table_v1(struct device *dev, struct opp_table *opp_table) nr = prop->length / sizeof(u32); if (nr % 2) { dev_err(dev, "%s: Invalid OPP table\n", __func__); - return -EINVAL; + ret = -EINVAL; + goto remove_static_opp; } - kref_init(&opp_table->list_kref); - val = prop->value; while (nr) { unsigned long freq = be32_to_cpup(val++) * 1000; unsigned long volt = be32_to_cpup(val++); + struct dev_pm_opp_data data = { + .freq = freq, + .u_volt = volt, + }; - ret = _opp_add_v1(opp_table, dev, freq, volt, false); + ret = _opp_add_v1(opp_table, dev, &data, false); if (ret) { dev_err(dev, "%s: Failed to add OPP %ld (%d)\n", - __func__, freq, ret); - _put_opp_list_kref(opp_table); - return ret; + __func__, data.freq, ret); + goto remove_static_opp; } nr -= 2; } + return 0; + +remove_static_opp: + _opp_remove_all_static(opp_table); + return ret; } -/** - * dev_pm_opp_of_add_table() - Initialize opp table from device tree - * @dev: device pointer used to lookup OPP table. - * - * Register the initial OPP table with the OPP library for given device. - * - * Return: - * 0 On success OR - * Duplicate OPPs (both freq and volt are same) and opp->available - * -EEXIST Freq are same and volt are different OR - * Duplicate OPPs (both freq and volt are same) and !opp->available - * -ENOMEM Memory allocation failure - * -ENODEV when 'operating-points' property is not found or is invalid data - * in device node. - * -ENODATA when empty 'operating-points' property is found - * -EINVAL when invalid entries are found in opp-v2 table - */ -int dev_pm_opp_of_add_table(struct device *dev) +static int _of_add_table_indexed(struct device *dev, int index) { struct opp_table *opp_table; - int ret; + int ret, count; - opp_table = dev_pm_opp_get_opp_table_indexed(dev, 0); - if (!opp_table) - return -ENOMEM; + if (index) { + /* + * If only one phandle is present, then the same OPP table + * applies for all index requests. + */ + count = of_count_phandle_with_args(dev->of_node, + "operating-points-v2", NULL); + if (count == 1) + index = 0; + } + + opp_table = _add_opp_table_indexed(dev, index, true); + if (IS_ERR(opp_table)) + return PTR_ERR(opp_table); /* * OPPs have two version of bindings now. Also try the old (v1) @@ -805,15 +1098,30 @@ int dev_pm_opp_of_add_table(struct device *dev) return ret; } -EXPORT_SYMBOL_GPL(dev_pm_opp_of_add_table); + +static void devm_pm_opp_of_table_release(void *data) +{ + dev_pm_opp_of_remove_table(data); +} + +static int _devm_of_add_table_indexed(struct device *dev, int index) +{ + int ret; + + ret = _of_add_table_indexed(dev, index); + if (ret) + return ret; + + return devm_add_action_or_reset(dev, devm_pm_opp_of_table_release, dev); +} /** - * dev_pm_opp_of_add_table_indexed() - Initialize indexed opp table from device tree + * devm_pm_opp_of_add_table() - Initialize opp table from device tree * @dev: device pointer used to lookup OPP table. - * @index: Index number. * - * Register the initial OPP table with the OPP library for given device only - * using the "operating-points-v2" property. + * Register the initial OPP table with the OPP library for given device. + * + * The opp_table structure will be freed after the device is destroyed. * * Return: * 0 On success OR @@ -826,34 +1134,64 @@ EXPORT_SYMBOL_GPL(dev_pm_opp_of_add_table); * -ENODATA when empty 'operating-points' property is found * -EINVAL when invalid entries are found in opp-v2 table */ -int dev_pm_opp_of_add_table_indexed(struct device *dev, int index) +int devm_pm_opp_of_add_table(struct device *dev) { - struct opp_table *opp_table; - int ret, count; - - if (index) { - /* - * If only one phandle is present, then the same OPP table - * applies for all index requests. - */ - count = of_count_phandle_with_args(dev->of_node, - "operating-points-v2", NULL); - if (count == 1) - index = 0; - } - - opp_table = dev_pm_opp_get_opp_table_indexed(dev, index); - if (!opp_table) - return -ENOMEM; + return _devm_of_add_table_indexed(dev, 0); +} +EXPORT_SYMBOL_GPL(devm_pm_opp_of_add_table); - ret = _of_add_opp_table_v2(dev, opp_table); - if (ret) - dev_pm_opp_put_opp_table(opp_table); +/** + * dev_pm_opp_of_add_table() - Initialize opp table from device tree + * @dev: device pointer used to lookup OPP table. + * + * Register the initial OPP table with the OPP library for given device. + * + * Return: + * 0 On success OR + * Duplicate OPPs (both freq and volt are same) and opp->available + * -EEXIST Freq are same and volt are different OR + * Duplicate OPPs (both freq and volt are same) and !opp->available + * -ENOMEM Memory allocation failure + * -ENODEV when 'operating-points' property is not found or is invalid data + * in device node. + * -ENODATA when empty 'operating-points' property is found + * -EINVAL when invalid entries are found in opp-v2 table + */ +int dev_pm_opp_of_add_table(struct device *dev) +{ + return _of_add_table_indexed(dev, 0); +} +EXPORT_SYMBOL_GPL(dev_pm_opp_of_add_table); - return ret; +/** + * dev_pm_opp_of_add_table_indexed() - Initialize indexed opp table from device tree + * @dev: device pointer used to lookup OPP table. + * @index: Index number. + * + * Register the initial OPP table with the OPP library for given device only + * using the "operating-points-v2" property. + * + * Return: Refer to dev_pm_opp_of_add_table() for return values. + */ +int dev_pm_opp_of_add_table_indexed(struct device *dev, int index) +{ + return _of_add_table_indexed(dev, index); } EXPORT_SYMBOL_GPL(dev_pm_opp_of_add_table_indexed); +/** + * devm_pm_opp_of_add_table_indexed() - Initialize indexed opp table from device tree + * @dev: device pointer used to lookup OPP table. + * @index: Index number. + * + * This is a resource-managed variant of dev_pm_opp_of_add_table_indexed(). + */ +int devm_pm_opp_of_add_table_indexed(struct device *dev, int index) +{ + return _devm_of_add_table_indexed(dev, index); +} +EXPORT_SYMBOL_GPL(devm_pm_opp_of_add_table_indexed); + /* CPU device specific helpers */ /** @@ -935,11 +1273,12 @@ EXPORT_SYMBOL_GPL(dev_pm_opp_of_cpumask_add_table); int dev_pm_opp_of_get_sharing_cpus(struct device *cpu_dev, struct cpumask *cpumask) { - struct device_node *np, *tmp_np, *cpu_np; - int cpu, ret = 0; + int cpu; /* Get OPP descriptor node */ - np = dev_pm_opp_of_get_opp_desc_node(cpu_dev); + struct device_node *np __free(device_node) = + dev_pm_opp_of_get_opp_desc_node(cpu_dev); + if (!np) { dev_dbg(cpu_dev, "%s: Couldn't find opp node.\n", __func__); return -ENOENT; @@ -949,39 +1288,36 @@ int dev_pm_opp_of_get_sharing_cpus(struct device *cpu_dev, /* OPPs are shared ? */ if (!of_property_read_bool(np, "opp-shared")) - goto put_cpu_node; + return 0; for_each_possible_cpu(cpu) { if (cpu == cpu_dev->id) continue; - cpu_np = of_cpu_device_node_get(cpu); + struct device_node *cpu_np __free(device_node) = + of_cpu_device_node_get(cpu); + if (!cpu_np) { dev_err(cpu_dev, "%s: failed to get cpu%d node\n", __func__, cpu); - ret = -ENOENT; - goto put_cpu_node; + return -ENOENT; } /* Get OPP descriptor node */ - tmp_np = _opp_of_get_opp_desc_node(cpu_np, 0); - of_node_put(cpu_np); + struct device_node *tmp_np __free(device_node) = + _opp_of_get_opp_desc_node(cpu_np, 0); + if (!tmp_np) { pr_err("%pOF: Couldn't find opp node\n", cpu_np); - ret = -ENOENT; - goto put_cpu_node; + return -ENOENT; } /* CPUs are sharing opp node */ if (np == tmp_np) cpumask_set_cpu(cpu, cpumask); - - of_node_put(tmp_np); } -put_cpu_node: - of_node_put(np); - return ret; + return 0; } EXPORT_SYMBOL_GPL(dev_pm_opp_of_get_sharing_cpus); @@ -998,38 +1334,79 @@ EXPORT_SYMBOL_GPL(dev_pm_opp_of_get_sharing_cpus); */ int of_get_required_opp_performance_state(struct device_node *np, int index) { - struct dev_pm_opp *opp; - struct device_node *required_np; - struct opp_table *opp_table; int pstate = -EINVAL; - required_np = of_parse_required_opp(np, index); + struct device_node *required_np __free(device_node) = + of_parse_required_opp(np, index); + if (!required_np) - return -EINVAL; + return -ENODEV; + + struct opp_table *opp_table __free(put_opp_table) = + _find_table_of_opp_np(required_np); - opp_table = _find_table_of_opp_np(required_np); if (IS_ERR(opp_table)) { pr_err("%s: Failed to find required OPP table %pOF: %ld\n", __func__, np, PTR_ERR(opp_table)); - goto put_required_np; + return PTR_ERR(opp_table); } - opp = _find_opp_of_np(opp_table, required_np); - if (opp) { - pstate = opp->pstate; - dev_pm_opp_put(opp); + /* The OPP tables must belong to a genpd */ + if (unlikely(!opp_table->is_genpd)) { + pr_err("%s: Performance state is only valid for genpds.\n", __func__); + return -EINVAL; } - dev_pm_opp_put_opp_table(opp_table); + struct dev_pm_opp *opp __free(put_opp) = + _find_opp_of_np(opp_table, required_np); -put_required_np: - of_node_put(required_np); + if (opp) { + if (opp->level == OPP_LEVEL_UNSET) { + pr_err("%s: OPP levels aren't available for %pOF\n", + __func__, np); + } else { + pstate = opp->level; + } + } return pstate; } EXPORT_SYMBOL_GPL(of_get_required_opp_performance_state); /** + * dev_pm_opp_of_has_required_opp - Find out if a required-opps exists. + * @dev: The device to investigate. + * + * Returns true if the device's node has a "operating-points-v2" property and if + * the corresponding node for the opp-table describes opp nodes that uses the + * "required-opps" property. + * + * Return: True if a required-opps is present, else false. + */ +bool dev_pm_opp_of_has_required_opp(struct device *dev) +{ + int count; + + struct device_node *opp_np __free(device_node) = + _opp_of_get_opp_desc_node(dev->of_node, 0); + + if (!opp_np) + return false; + + struct device_node *np __free(device_node) = + of_get_next_available_child(opp_np, NULL); + + if (!np) { + dev_warn(dev, "Empty OPP table\n"); + return false; + } + + count = of_count_phandle_with_args(np, "required-opps", NULL); + + return count > 0; +} + +/** * dev_pm_opp_get_of_node() - Gets the DT node corresponding to an opp * @opp: opp for which DT node has to be returned for * @@ -1047,3 +1424,176 @@ struct device_node *dev_pm_opp_get_of_node(struct dev_pm_opp *opp) return of_node_get(opp->np); } EXPORT_SYMBOL_GPL(dev_pm_opp_get_of_node); + +/* + * Callback function provided to the Energy Model framework upon registration. + * It provides the power used by @dev at @kHz if it is the frequency of an + * existing OPP, or at the frequency of the first OPP above @kHz otherwise + * (see dev_pm_opp_find_freq_ceil()). This function updates @kHz to the ceiled + * frequency and @uW to the associated power. + * + * Returns 0 on success or a proper -EINVAL value in case of error. + */ +static int __maybe_unused +_get_dt_power(struct device *dev, unsigned long *uW, unsigned long *kHz) +{ + unsigned long opp_freq, opp_power; + + /* Find the right frequency and related OPP */ + opp_freq = *kHz * 1000; + + struct dev_pm_opp *opp __free(put_opp) = + dev_pm_opp_find_freq_ceil(dev, &opp_freq); + + if (IS_ERR(opp)) + return -EINVAL; + + opp_power = dev_pm_opp_get_power(opp); + if (!opp_power) + return -EINVAL; + + *kHz = opp_freq / 1000; + *uW = opp_power; + + return 0; +} + +/** + * dev_pm_opp_calc_power() - Calculate power value for device with EM + * @dev : Device for which an Energy Model has to be registered + * @uW : New power value that is calculated + * @kHz : Frequency for which the new power is calculated + * + * This computes the power estimated by @dev at @kHz if it is the frequency + * of an existing OPP, or at the frequency of the first OPP above @kHz otherwise + * (see dev_pm_opp_find_freq_ceil()). This function updates @kHz to the ceiled + * frequency and @uW to the associated power. The power is estimated as + * P = C * V^2 * f with C being the device's capacitance and V and f + * respectively the voltage and frequency of the OPP. + * It is also used as a callback function provided to the Energy Model + * framework upon registration. + * + * Returns -EINVAL if the power calculation failed because of missing + * parameters, 0 otherwise. + */ +int dev_pm_opp_calc_power(struct device *dev, unsigned long *uW, + unsigned long *kHz) +{ + unsigned long mV, Hz; + u32 cap; + u64 tmp; + int ret; + + struct device_node *np __free(device_node) = of_node_get(dev->of_node); + + if (!np) + return -EINVAL; + + ret = of_property_read_u32(np, "dynamic-power-coefficient", &cap); + if (ret) + return -EINVAL; + + Hz = *kHz * 1000; + + struct dev_pm_opp *opp __free(put_opp) = + dev_pm_opp_find_freq_ceil(dev, &Hz); + + if (IS_ERR(opp)) + return -EINVAL; + + mV = dev_pm_opp_get_voltage(opp) / 1000; + if (!mV) + return -EINVAL; + + tmp = (u64)cap * mV * mV * (Hz / 1000000); + /* Provide power in micro-Watts */ + do_div(tmp, 1000000); + + *uW = (unsigned long)tmp; + *kHz = Hz / 1000; + + return 0; +} +EXPORT_SYMBOL_GPL(dev_pm_opp_calc_power); + +static bool _of_has_opp_microwatt_property(struct device *dev) +{ + unsigned long freq = 0; + + /* Check if at least one OPP has needed property */ + struct dev_pm_opp *opp __free(put_opp) = + dev_pm_opp_find_freq_ceil(dev, &freq); + + if (IS_ERR(opp)) + return false; + + return !!dev_pm_opp_get_power(opp); +} + +/** + * dev_pm_opp_of_register_em() - Attempt to register an Energy Model + * @dev : Device for which an Energy Model has to be registered + * @cpus : CPUs for which an Energy Model has to be registered. For + * other type of devices it should be set to NULL. + * + * This checks whether the "dynamic-power-coefficient" devicetree property has + * been specified, and tries to register an Energy Model with it if it has. + * Having this property means the voltages are known for OPPs and the EM + * might be calculated. + */ +int dev_pm_opp_of_register_em(struct device *dev, struct cpumask *cpus) +{ + struct em_data_callback em_cb; + int ret, nr_opp; + u32 cap; + + if (IS_ERR_OR_NULL(dev)) + return -EINVAL; + + struct device_node *np __free(device_node) = of_node_get(dev->of_node); + + if (!np) { + ret = -EINVAL; + goto failed; + } + + nr_opp = dev_pm_opp_get_opp_count(dev); + if (nr_opp <= 0) { + ret = -EINVAL; + goto failed; + } + + /* First, try to find more precised Energy Model in DT */ + if (_of_has_opp_microwatt_property(dev)) { + EM_SET_ACTIVE_POWER_CB(em_cb, _get_dt_power); + goto register_em; + } + + /* + * Register an EM only if the 'dynamic-power-coefficient' property is + * set in devicetree. It is assumed the voltage values are known if that + * property is set since it is useless otherwise. If voltages are not + * known, just let the EM registration fail with an error to alert the + * user about the inconsistent configuration. + */ + ret = of_property_read_u32(np, "dynamic-power-coefficient", &cap); + if (ret || !cap) { + dev_dbg(dev, "Couldn't find proper 'dynamic-power-coefficient' in DT\n"); + ret = -EINVAL; + goto failed; + } + + EM_SET_ACTIVE_POWER_CB(em_cb, dev_pm_opp_calc_power); + +register_em: + ret = em_dev_register_perf_domain(dev, nr_opp, &em_cb, cpus, true); + if (ret) + goto failed; + + return 0; + +failed: + dev_dbg(dev, "Couldn't register Energy Model %d\n", ret); + return ret; +} +EXPORT_SYMBOL_GPL(dev_pm_opp_of_register_em); |
