opp: Add support for parsing interconnect bandwidth

The OPP bindings now support bandwidth values, so add support to parse it
from device tree and store it into the new dev_pm_opp_icc_bw struct, which
is part of the dev_pm_opp.

Signed-off-by: Georgi Djakov <georgi.djakov@linaro.org>
Reviewed-by: Matthias Kaehlcke <mka@chromium.org>
[ Viresh: Create _read_bw() and use it, renamed _of_find_icc_paths() to
	  dev_pm_opp_of_find_icc_paths(), exported it and made opp_table
	  argument optional. Also drop the depends on from Kconfig. ]
Signed-off-by: Viresh Kumar <viresh.kumar@linaro.org>
This commit is contained in:
Georgi Djakov 2020-05-12 15:53:21 +03:00 committed by Viresh Kumar
parent 90562c8182
commit 6d3f922c46
4 changed files with 162 additions and 5 deletions

View File

@ -999,6 +999,12 @@ static struct opp_table *_allocate_opp_table(struct device *dev, int index)
ret);
}
/* Find interconnect path(s) for the device */
ret = dev_pm_opp_of_find_icc_paths(dev, opp_table);
if (ret)
dev_warn(dev, "%s: Error finding interconnect paths: %d\n",
__func__, ret);
BLOCKING_INIT_NOTIFIER_HEAD(&opp_table->head);
INIT_LIST_HEAD(&opp_table->opp_list);
kref_init(&opp_table->kref);
@ -1057,6 +1063,7 @@ static void _opp_table_kref_release(struct kref *kref)
{
struct opp_table *opp_table = container_of(kref, struct opp_table, kref);
struct opp_device *opp_dev, *temp;
int i;
_of_clear_opp_table(opp_table);
@ -1064,6 +1071,12 @@ static void _opp_table_kref_release(struct kref *kref)
if (!IS_ERR(opp_table->clk))
clk_put(opp_table->clk);
if (opp_table->paths) {
for (i = 0; i < opp_table->path_count; i++)
icc_put(opp_table->paths[i]);
kfree(opp_table->paths);
}
WARN_ON(!list_empty(&opp_table->opp_list));
list_for_each_entry_safe(opp_dev, temp, &opp_table->dev_list, node) {
@ -1243,19 +1256,23 @@ EXPORT_SYMBOL_GPL(dev_pm_opp_remove_all_dynamic);
struct dev_pm_opp *_opp_allocate(struct opp_table *table)
{
struct dev_pm_opp *opp;
int count, supply_size;
int supply_count, supply_size, icc_size;
/* Allocate space for at least one supply */
count = table->regulator_count > 0 ? table->regulator_count : 1;
supply_size = sizeof(*opp->supplies) * count;
supply_count = table->regulator_count > 0 ? table->regulator_count : 1;
supply_size = sizeof(*opp->supplies) * supply_count;
icc_size = sizeof(*opp->bandwidth) * table->path_count;
/* allocate new OPP node and supplies structures */
opp = kzalloc(sizeof(*opp) + supply_size, GFP_KERNEL);
opp = kzalloc(sizeof(*opp) + supply_size + icc_size, GFP_KERNEL);
if (!opp)
return NULL;
/* Put the supplies at the end of the OPP structure as an empty array */
opp->supplies = (struct dev_pm_opp_supply *)(opp + 1);
if (icc_size)
opp->bandwidth = (struct dev_pm_opp_icc_bw *)(opp->supplies + supply_count);
INIT_LIST_HEAD(&opp->node);
return opp;
@ -1290,6 +1307,9 @@ int _opp_compare_key(struct dev_pm_opp *opp1, struct dev_pm_opp *opp2)
{
if (opp1->rate != opp2->rate)
return opp1->rate < opp2->rate ? -1 : 1;
if (opp1->bandwidth && opp2->bandwidth &&
opp1->bandwidth[0].peak != opp2->bandwidth[0].peak)
return opp1->bandwidth[0].peak < opp2->bandwidth[0].peak ? -1 : 1;
if (opp1->level != opp2->level)
return opp1->level < opp2->level ? -1 : 1;
return 0;

View File

@ -332,6 +332,62 @@ free_required_opps:
return ret;
}
int dev_pm_opp_of_find_icc_paths(struct device *dev,
struct opp_table *opp_table)
{
struct device_node *np;
int ret = 0, i, count, num_paths;
struct icc_path **paths;
np = of_node_get(dev->of_node);
if (!np)
return 0;
count = of_count_phandle_with_args(np, "interconnects",
"#interconnect-cells");
of_node_put(np);
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 = PTR_ERR(paths[i]);
if (ret != -EPROBE_DEFER) {
dev_err(dev, "%s: Unable to get path%d: %d\n",
__func__, i, ret);
}
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)
{
@ -521,9 +577,45 @@ void dev_pm_opp_of_remove_table(struct device *dev)
}
EXPORT_SYMBOL_GPL(dev_pm_opp_of_remove_table);
static int _read_bw(struct dev_pm_opp *new_opp, 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);
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 device_node *np,
bool *rate_not_available)
{
bool found = false;
u64 rate;
int ret;
@ -535,10 +627,30 @@ static int _read_opp_key(struct dev_pm_opp *new_opp, struct device_node *np,
* bit guaranteed in clk API.
*/
new_opp->rate = (unsigned long)rate;
found = true;
}
*rate_not_available = !!ret;
of_property_read_u32(np, "opp-level", &new_opp->level);
/*
* 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, np, true);
if (!ret) {
found = true;
ret = _read_bw(new_opp, 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;
}

View File

@ -12,6 +12,7 @@
#define __DRIVER_OPP_H__
#include <linux/device.h>
#include <linux/interconnect.h>
#include <linux/kernel.h>
#include <linux/kref.h>
#include <linux/list.h>
@ -59,6 +60,7 @@ extern struct list_head opp_tables;
* @rate: Frequency in hertz
* @level: Performance level
* @supplies: Power supplies voltage/current values
* @bandwidth: Interconnect bandwidth values
* @clock_latency_ns: Latency (in nanoseconds) of switching to this OPP's
* frequency from any other OPP's frequency.
* @required_opps: List of OPPs that are required by this OPP.
@ -81,6 +83,7 @@ struct dev_pm_opp {
unsigned int level;
struct dev_pm_opp_supply *supplies;
struct dev_pm_opp_icc_bw *bandwidth;
unsigned long clock_latency_ns;
@ -146,6 +149,8 @@ enum opp_table_access {
* @regulator_count: Number of power supply regulators. Its value can be -1
* (uninitialized), 0 (no opp-microvolt property) or > 0 (has opp-microvolt
* property).
* @paths: Interconnect path handles
* @path_count: Number of interconnect paths
* @genpd_performance_state: Device's power domain support performance state.
* @is_genpd: Marks if the OPP table belongs to a genpd.
* @set_opp: Platform specific set_opp callback
@ -189,6 +194,8 @@ struct opp_table {
struct clk *clk;
struct regulator **regulators;
int regulator_count;
struct icc_path **paths;
unsigned int path_count;
bool genpd_performance_state;
bool is_genpd;

View File

@ -41,6 +41,18 @@ struct dev_pm_opp_supply {
unsigned long u_amp;
};
/**
* struct dev_pm_opp_icc_bw - Interconnect bandwidth values
* @avg: Average bandwidth corresponding to this OPP (in icc units)
* @peak: Peak bandwidth corresponding to this OPP (in icc units)
*
* This structure stores the bandwidth values for a single interconnect path.
*/
struct dev_pm_opp_icc_bw {
u32 avg;
u32 peak;
};
/**
* struct dev_pm_opp_info - OPP freq/voltage/current values
* @rate: Target clk rate in hz
@ -360,6 +372,7 @@ int dev_pm_opp_of_get_sharing_cpus(struct device *cpu_dev, struct cpumask *cpuma
struct device_node *dev_pm_opp_of_get_opp_desc_node(struct device *dev);
struct device_node *dev_pm_opp_get_of_node(struct dev_pm_opp *opp);
int of_get_required_opp_performance_state(struct device_node *np, int index);
int dev_pm_opp_of_find_icc_paths(struct device *dev, struct opp_table *opp_table);
void dev_pm_opp_of_register_em(struct cpumask *cpus);
#else
static inline int dev_pm_opp_of_add_table(struct device *dev)
@ -408,6 +421,11 @@ static inline int of_get_required_opp_performance_state(struct device_node *np,
{
return -ENOTSUPP;
}
static inline int dev_pm_opp_of_find_icc_paths(struct device *dev, struct opp_table *opp_table)
{
return -ENOTSUPP;
}
#endif
#endif /* __LINUX_OPP_H__ */