mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2024-08-21 08:19:28 +00:00
![Dave Gerlach](/assets/img/avatar_default.png)
Currently the _of_add_opp_table_v2 call loops through the OPP nodes in the operating-points-v2 table in the device tree and calls _opp_add_static_v2 for each to add them to the table. It counts each iteration through this loop as an added OPP, however there are cases where _opp_add_static_v2() returns 0 but no new OPP is added to the list. This can happen while adding duplicate OPP or if the OPP isn't supported by hardware. Because of this the count variable will contain the number of OPP nodes in the table in device tree but not necessarily the ones that are actually added. As this count value is what is checked to determine if there are any valid OPPs, if a platform has an operating-points-v2 table with all OPP nodes containing opp-supported-hw values that are not currently supported, then _of_add_opp_table_v2 will fail to abort as it should due to an empty table. Additionally, since commit3ba98324e8
("PM / OPP: Get performance state using genpd helper"), the same count variable is compared against the number of OPPs containing performance states and requires that either all or none have pstates set, however in the case of any opp table that has any entries that do not get added by _opp_add_static_v2 due to incompatible opp-supported-hw fields, these numbers will not match and _of_add_opp_table_v2 will incorrectly fail. We need to clearly identify all the three cases (success, failure, unsupported/duplicate OPPs) and then increment count only on success case. Change return type of _opp_add_static_v2() to return the pointer to the newly added OPP instead of an integer. This routine now returns a valid pointer if the OPP is really added, NULL for unsupported or duplicate OPPs, and error value cased as a pointer on errors. Ideally the fixes tag in this commit should point back to the commit that introduced OPP v2 initially, as that's where we started incorrectly accounting for duplicate OPPs: commit274659029c
("PM / OPP: Add support to parse "operating-points-v2" bindings") But it wasn't a real problem until recently as the count was only used to check if any OPPs are added or not. And so this commit points to a rather recent commit where we added more code that depends on the value of "count". Fixes:3ba98324e8
("PM / OPP: Get performance state using genpd helper") Reported-by: Dave Gerlach <d-gerlach@ti.com> Reported-by: Niklas Cassel <niklas.cassel@linaro.org> Tested-by: Niklas Cassel <niklas.cassel@linaro.org> Signed-off-by: Dave Gerlach <d-gerlach@ti.com> Signed-off-by: Viresh Kumar <viresh.kumar@linaro.org>
802 lines
20 KiB
C
802 lines
20 KiB
C
/*
|
|
* Generic OPP OF helpers
|
|
*
|
|
* Copyright (C) 2009-2010 Texas Instruments Incorporated.
|
|
* 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
|
|
|
|
#include <linux/cpu.h>
|
|
#include <linux/errno.h>
|
|
#include <linux/device.h>
|
|
#include <linux/of_device.h>
|
|
#include <linux/pm_domain.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/export.h>
|
|
|
|
#include "opp.h"
|
|
|
|
/*
|
|
* Returns opp descriptor node for a device node, caller must
|
|
* do of_node_put().
|
|
*/
|
|
static struct device_node *_opp_of_get_opp_desc_node(struct device_node *np,
|
|
int index)
|
|
{
|
|
/* "operating-points-v2" can be an array for power domain providers */
|
|
return of_parse_phandle(np, "operating-points-v2", index);
|
|
}
|
|
|
|
/* Returns opp descriptor node for a device, caller must do of_node_put() */
|
|
struct device_node *dev_pm_opp_of_get_opp_desc_node(struct device *dev)
|
|
{
|
|
return _opp_of_get_opp_desc_node(dev->of_node, 0);
|
|
}
|
|
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);
|
|
if (!np)
|
|
return NULL;
|
|
|
|
list_for_each_entry(opp_table, &opp_tables, node) {
|
|
if (opp_table->np == np) {
|
|
/*
|
|
* Multiple devices can point to the same OPP table and
|
|
* so will have same node-pointer, np.
|
|
*
|
|
* 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;
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
of_node_put(np);
|
|
|
|
return managed_table;
|
|
}
|
|
|
|
void _of_init_opp_table(struct opp_table *opp_table, struct device *dev,
|
|
int index)
|
|
{
|
|
struct device_node *np, *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);
|
|
if (!np)
|
|
return;
|
|
|
|
if (!of_property_read_u32(np, "clock-latency", &val))
|
|
opp_table->clock_latency_ns_max = val;
|
|
of_property_read_u32(np, "voltage-tolerance",
|
|
&opp_table->voltage_tolerance_v1);
|
|
|
|
/* Get OPP table node */
|
|
opp_np = _opp_of_get_opp_desc_node(np, index);
|
|
of_node_put(np);
|
|
|
|
if (!opp_np)
|
|
return;
|
|
|
|
if (of_property_read_bool(opp_np, "opp-shared"))
|
|
opp_table->shared_opp = OPP_TABLE_ACCESS_SHARED;
|
|
else
|
|
opp_table->shared_opp = OPP_TABLE_ACCESS_EXCLUSIVE;
|
|
|
|
opp_table->np = opp_np;
|
|
|
|
of_node_put(opp_np);
|
|
}
|
|
|
|
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;
|
|
|
|
if (!opp_table->supported_hw) {
|
|
/*
|
|
* In the case that no supported_hw has been set by the
|
|
* platform but there is an opp-supported-hw value set for
|
|
* 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))
|
|
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;
|
|
}
|
|
|
|
/* Both of these are bitwise masks of the versions */
|
|
if (!(version & opp_table->supported_hw[count]))
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static int opp_parse_supplies(struct dev_pm_opp *opp, struct device *dev,
|
|
struct opp_table *opp_table)
|
|
{
|
|
u32 *microvolt, *microamp = NULL;
|
|
int supplies, vcount, icount, ret, i, j;
|
|
struct property *prop = NULL;
|
|
char name[NAME_MAX];
|
|
|
|
supplies = opp_table->regulator_count ? opp_table->regulator_count : 1;
|
|
|
|
/* Search for "opp-microvolt-<name>" */
|
|
if (opp_table->prop_name) {
|
|
snprintf(name, sizeof(name), "opp-microvolt-%s",
|
|
opp_table->prop_name);
|
|
prop = of_find_property(opp->np, name, NULL);
|
|
}
|
|
|
|
if (!prop) {
|
|
/* Search for "opp-microvolt" */
|
|
sprintf(name, "opp-microvolt");
|
|
prop = of_find_property(opp->np, name, NULL);
|
|
|
|
/* Missing property isn't a problem, but an invalid entry is */
|
|
if (!prop) {
|
|
if (!opp_table->regulator_count)
|
|
return 0;
|
|
|
|
dev_err(dev, "%s: opp-microvolt missing although OPP managing regulators\n",
|
|
__func__);
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
/* 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;
|
|
}
|
|
|
|
microvolt = kmalloc_array(vcount, sizeof(*microvolt), GFP_KERNEL);
|
|
if (!microvolt)
|
|
return -ENOMEM;
|
|
|
|
ret = of_property_read_u32_array(opp->np, name, microvolt, vcount);
|
|
if (ret) {
|
|
dev_err(dev, "%s: error parsing %s: %d\n", __func__, name, ret);
|
|
ret = -EINVAL;
|
|
goto free_microvolt;
|
|
}
|
|
|
|
/* 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 (!prop) {
|
|
/* Search for "opp-microamp" */
|
|
sprintf(name, "opp-microamp");
|
|
prop = of_find_property(opp->np, name, NULL);
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
microamp = kmalloc_array(icount, sizeof(*microamp), GFP_KERNEL);
|
|
if (!microamp) {
|
|
ret = -EINVAL;
|
|
goto free_microvolt;
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
for (i = 0, j = 0; i < supplies; i++) {
|
|
opp->supplies[i].u_volt = microvolt[j++];
|
|
|
|
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++];
|
|
}
|
|
|
|
if (microamp)
|
|
opp->supplies[i].u_amp = microamp[i];
|
|
}
|
|
|
|
free_microamp:
|
|
kfree(microamp);
|
|
free_microvolt:
|
|
kfree(microvolt);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* dev_pm_opp_of_remove_table() - Free OPP table entries created from static DT
|
|
* entries
|
|
* @dev: device pointer used to lookup OPP table.
|
|
*
|
|
* Free OPPs created using static entries present in DT.
|
|
*/
|
|
void dev_pm_opp_of_remove_table(struct device *dev)
|
|
{
|
|
_dev_pm_opp_find_and_remove_table(dev);
|
|
}
|
|
EXPORT_SYMBOL_GPL(dev_pm_opp_of_remove_table);
|
|
|
|
/**
|
|
* _opp_add_static_v2() - Allocate static OPPs (As per 'v2' DT bindings)
|
|
* @opp_table: OPP table
|
|
* @dev: device for which we do this operation
|
|
* @np: device node
|
|
*
|
|
* This function adds an opp definition to the opp table and returns status. The
|
|
* opp can be controlled using dev_pm_opp_enable/disable functions and may be
|
|
* removed by dev_pm_opp_remove.
|
|
*
|
|
* Return:
|
|
* Valid OPP pointer:
|
|
* On success
|
|
* NULL:
|
|
* Duplicate OPPs (both freq and volt are same) and opp->available
|
|
* OR if the OPP is not supported by hardware.
|
|
* ERR_PTR(-EEXIST):
|
|
* Freq are same and volt are different OR
|
|
* Duplicate OPPs (both freq and volt are same) and !opp->available
|
|
* ERR_PTR(-ENOMEM):
|
|
* Memory allocation failure
|
|
* ERR_PTR(-EINVAL):
|
|
* Failed parsing the OPP node
|
|
*/
|
|
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);
|
|
if (ret < 0) {
|
|
/* "opp-hz" is optional for devices like power domains. */
|
|
if (!of_find_property(dev->of_node, "#power-domain-cells",
|
|
NULL)) {
|
|
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;
|
|
}
|
|
|
|
/* 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);
|
|
goto free_opp;
|
|
}
|
|
|
|
new_opp->turbo = of_property_read_bool(np, "turbo-mode");
|
|
|
|
new_opp->np = np;
|
|
new_opp->dynamic = false;
|
|
new_opp->available = true;
|
|
|
|
if (!of_property_read_u32(np, "clock-latency-ns", &val))
|
|
new_opp->clock_latency_ns = val;
|
|
|
|
new_opp->pstate = of_genpd_opp_to_performance_state(dev, np);
|
|
|
|
ret = opp_parse_supplies(new_opp, dev, opp_table);
|
|
if (ret)
|
|
goto free_opp;
|
|
|
|
ret = _opp_add(dev, new_opp, opp_table, rate_not_available);
|
|
if (ret) {
|
|
/* Don't return error for duplicate OPPs */
|
|
if (ret == -EBUSY)
|
|
ret = 0;
|
|
goto free_opp;
|
|
}
|
|
|
|
/* 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);
|
|
} else {
|
|
new_opp->suspend = true;
|
|
opp_table->suspend_opp = new_opp;
|
|
}
|
|
}
|
|
|
|
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,
|
|
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);
|
|
|
|
/*
|
|
* Notify the changes in the availability of the operable
|
|
* frequency/voltage list.
|
|
*/
|
|
blocking_notifier_call_chain(&opp_table->head, OPP_EVENT_ADD, new_opp);
|
|
return new_opp;
|
|
|
|
free_opp:
|
|
_opp_free(new_opp);
|
|
|
|
return ERR_PTR(ret);
|
|
}
|
|
|
|
/* 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;
|
|
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;
|
|
}
|
|
|
|
kref_init(&opp_table->list_kref);
|
|
|
|
/* We have opp-table node now, iterate over it and add OPPs */
|
|
for_each_available_child_of_node(opp_table->np, np) {
|
|
opp = _opp_add_static_v2(opp_table, dev, np);
|
|
if (IS_ERR(opp)) {
|
|
ret = PTR_ERR(opp);
|
|
dev_err(dev, "%s: Failed to add OPP, %d\n", __func__,
|
|
ret);
|
|
of_node_put(np);
|
|
goto put_list_kref;
|
|
} 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);
|
|
ret = -ENOENT;
|
|
goto put_list_kref;
|
|
}
|
|
|
|
if (pstate_count)
|
|
opp_table->genpd_performance_state = true;
|
|
|
|
opp_table->parsed_static_opps = true;
|
|
|
|
return 0;
|
|
|
|
put_list_kref:
|
|
_put_opp_list_kref(opp_table);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* Initializes OPP tables based on old-deprecated bindings */
|
|
static int _of_add_opp_table_v1(struct device *dev, struct opp_table *opp_table)
|
|
{
|
|
const struct property *prop;
|
|
const __be32 *val;
|
|
int nr, ret = 0;
|
|
|
|
prop = of_find_property(dev->of_node, "operating-points", NULL);
|
|
if (!prop)
|
|
return -ENODEV;
|
|
if (!prop->value)
|
|
return -ENODATA;
|
|
|
|
/*
|
|
* Each OPP is a set of tuples consisting of frequency and
|
|
* voltage like <freq-kHz vol-uV>.
|
|
*/
|
|
nr = prop->length / sizeof(u32);
|
|
if (nr % 2) {
|
|
dev_err(dev, "%s: Invalid OPP table\n", __func__);
|
|
return -EINVAL;
|
|
}
|
|
|
|
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++);
|
|
|
|
ret = _opp_add_v1(opp_table, dev, freq, volt, 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;
|
|
}
|
|
nr -= 2;
|
|
}
|
|
|
|
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)
|
|
{
|
|
struct opp_table *opp_table;
|
|
int ret;
|
|
|
|
opp_table = dev_pm_opp_get_opp_table_indexed(dev, 0);
|
|
if (!opp_table)
|
|
return -ENOMEM;
|
|
|
|
/*
|
|
* OPPs have two version of bindings now. Also try the old (v1)
|
|
* bindings for backward compatibility with older dtbs.
|
|
*/
|
|
if (opp_table->np)
|
|
ret = _of_add_opp_table_v2(dev, opp_table);
|
|
else
|
|
ret = _of_add_opp_table_v1(dev, opp_table);
|
|
|
|
if (ret)
|
|
dev_pm_opp_put_opp_table(opp_table);
|
|
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL_GPL(dev_pm_opp_of_add_table);
|
|
|
|
/**
|
|
* 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:
|
|
* 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_indexed(struct device *dev, int index)
|
|
{
|
|
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)
|
|
return -ENODEV;
|
|
|
|
index = 0;
|
|
}
|
|
|
|
opp_table = dev_pm_opp_get_opp_table_indexed(dev, index);
|
|
if (!opp_table)
|
|
return -ENOMEM;
|
|
|
|
ret = _of_add_opp_table_v2(dev, opp_table);
|
|
if (ret)
|
|
dev_pm_opp_put_opp_table(opp_table);
|
|
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL_GPL(dev_pm_opp_of_add_table_indexed);
|
|
|
|
/* CPU device specific helpers */
|
|
|
|
/**
|
|
* dev_pm_opp_of_cpumask_remove_table() - Removes OPP table for @cpumask
|
|
* @cpumask: cpumask for which OPP table needs to be removed
|
|
*
|
|
* This removes the OPP tables for CPUs present in the @cpumask.
|
|
* This should be used only to remove static entries created from DT.
|
|
*/
|
|
void dev_pm_opp_of_cpumask_remove_table(const struct cpumask *cpumask)
|
|
{
|
|
_dev_pm_opp_cpumask_remove_table(cpumask, -1);
|
|
}
|
|
EXPORT_SYMBOL_GPL(dev_pm_opp_of_cpumask_remove_table);
|
|
|
|
/**
|
|
* dev_pm_opp_of_cpumask_add_table() - Adds OPP table for @cpumask
|
|
* @cpumask: cpumask for which OPP table needs to be added.
|
|
*
|
|
* This adds the OPP tables for CPUs present in the @cpumask.
|
|
*/
|
|
int dev_pm_opp_of_cpumask_add_table(const struct cpumask *cpumask)
|
|
{
|
|
struct device *cpu_dev;
|
|
int cpu, ret;
|
|
|
|
if (WARN_ON(cpumask_empty(cpumask)))
|
|
return -ENODEV;
|
|
|
|
for_each_cpu(cpu, cpumask) {
|
|
cpu_dev = get_cpu_device(cpu);
|
|
if (!cpu_dev) {
|
|
pr_err("%s: failed to get cpu%d device\n", __func__,
|
|
cpu);
|
|
ret = -ENODEV;
|
|
goto remove_table;
|
|
}
|
|
|
|
ret = dev_pm_opp_of_add_table(cpu_dev);
|
|
if (ret) {
|
|
/*
|
|
* OPP may get registered dynamically, don't print error
|
|
* message here.
|
|
*/
|
|
pr_debug("%s: couldn't find opp table for cpu:%d, %d\n",
|
|
__func__, cpu, ret);
|
|
|
|
goto remove_table;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
|
|
remove_table:
|
|
/* Free all other OPPs */
|
|
_dev_pm_opp_cpumask_remove_table(cpumask, cpu);
|
|
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL_GPL(dev_pm_opp_of_cpumask_add_table);
|
|
|
|
/*
|
|
* Works only for OPP v2 bindings.
|
|
*
|
|
* Returns -ENOENT if operating-points-v2 bindings aren't supported.
|
|
*/
|
|
/**
|
|
* dev_pm_opp_of_get_sharing_cpus() - Get cpumask of CPUs sharing OPPs with
|
|
* @cpu_dev using operating-points-v2
|
|
* bindings.
|
|
*
|
|
* @cpu_dev: CPU device for which we do this operation
|
|
* @cpumask: cpumask to update with information of sharing CPUs
|
|
*
|
|
* This updates the @cpumask with CPUs that are sharing OPPs with @cpu_dev.
|
|
*
|
|
* Returns -ENOENT if operating-points-v2 isn't present for @cpu_dev.
|
|
*/
|
|
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;
|
|
|
|
/* Get OPP descriptor node */
|
|
np = 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;
|
|
}
|
|
|
|
cpumask_set_cpu(cpu_dev->id, cpumask);
|
|
|
|
/* OPPs are shared ? */
|
|
if (!of_property_read_bool(np, "opp-shared"))
|
|
goto put_cpu_node;
|
|
|
|
for_each_possible_cpu(cpu) {
|
|
if (cpu == cpu_dev->id)
|
|
continue;
|
|
|
|
cpu_np = 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;
|
|
}
|
|
|
|
/* Get OPP descriptor node */
|
|
tmp_np = _opp_of_get_opp_desc_node(cpu_np, 0);
|
|
of_node_put(cpu_np);
|
|
if (!tmp_np) {
|
|
pr_err("%pOF: Couldn't find opp node\n", cpu_np);
|
|
ret = -ENOENT;
|
|
goto put_cpu_node;
|
|
}
|
|
|
|
/* 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;
|
|
}
|
|
EXPORT_SYMBOL_GPL(dev_pm_opp_of_get_sharing_cpus);
|
|
|
|
/**
|
|
* of_dev_pm_opp_find_required_opp() - Search for required OPP.
|
|
* @dev: The device whose OPP node is referenced by the 'np' DT node.
|
|
* @np: Node that contains the "required-opps" property.
|
|
*
|
|
* Returns the OPP of the device 'dev', whose phandle is present in the "np"
|
|
* node. Although the "required-opps" property supports having multiple
|
|
* phandles, this helper routine only parses the very first phandle in the list.
|
|
*
|
|
* Return: Matching opp, else returns ERR_PTR in case of error and should be
|
|
* handled using IS_ERR.
|
|
*
|
|
* The callers are required to call dev_pm_opp_put() for the returned OPP after
|
|
* use.
|
|
*/
|
|
struct dev_pm_opp *of_dev_pm_opp_find_required_opp(struct device *dev,
|
|
struct device_node *np)
|
|
{
|
|
struct dev_pm_opp *temp_opp, *opp = ERR_PTR(-ENODEV);
|
|
struct device_node *required_np;
|
|
struct opp_table *opp_table;
|
|
|
|
opp_table = _find_opp_table(dev);
|
|
if (IS_ERR(opp_table))
|
|
return ERR_CAST(opp_table);
|
|
|
|
required_np = of_parse_phandle(np, "required-opps", 0);
|
|
if (unlikely(!required_np)) {
|
|
dev_err(dev, "Unable to parse required-opps\n");
|
|
goto put_opp_table;
|
|
}
|
|
|
|
mutex_lock(&opp_table->lock);
|
|
|
|
list_for_each_entry(temp_opp, &opp_table->opp_list, node) {
|
|
if (temp_opp->available && temp_opp->np == required_np) {
|
|
opp = temp_opp;
|
|
|
|
/* Increment the reference count of OPP */
|
|
dev_pm_opp_get(opp);
|
|
break;
|
|
}
|
|
}
|
|
|
|
mutex_unlock(&opp_table->lock);
|
|
|
|
of_node_put(required_np);
|
|
put_opp_table:
|
|
dev_pm_opp_put_opp_table(opp_table);
|
|
|
|
return opp;
|
|
}
|
|
EXPORT_SYMBOL_GPL(of_dev_pm_opp_find_required_opp);
|
|
|
|
/**
|
|
* 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
|
|
*
|
|
* Return: DT node corresponding to the opp, else 0 on success.
|
|
*
|
|
* The caller needs to put the node with of_node_put() after using it.
|
|
*/
|
|
struct device_node *dev_pm_opp_get_of_node(struct dev_pm_opp *opp)
|
|
{
|
|
if (IS_ERR_OR_NULL(opp)) {
|
|
pr_err("%s: Invalid parameters\n", __func__);
|
|
return NULL;
|
|
}
|
|
|
|
return of_node_get(opp->np);
|
|
}
|
|
EXPORT_SYMBOL_GPL(dev_pm_opp_get_of_node);
|