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:
parent
90562c8182
commit
6d3f922c46
|
@ -999,6 +999,12 @@ static struct opp_table *_allocate_opp_table(struct device *dev, int index)
|
||||||
ret);
|
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);
|
BLOCKING_INIT_NOTIFIER_HEAD(&opp_table->head);
|
||||||
INIT_LIST_HEAD(&opp_table->opp_list);
|
INIT_LIST_HEAD(&opp_table->opp_list);
|
||||||
kref_init(&opp_table->kref);
|
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_table *opp_table = container_of(kref, struct opp_table, kref);
|
||||||
struct opp_device *opp_dev, *temp;
|
struct opp_device *opp_dev, *temp;
|
||||||
|
int i;
|
||||||
|
|
||||||
_of_clear_opp_table(opp_table);
|
_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))
|
if (!IS_ERR(opp_table->clk))
|
||||||
clk_put(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));
|
WARN_ON(!list_empty(&opp_table->opp_list));
|
||||||
|
|
||||||
list_for_each_entry_safe(opp_dev, temp, &opp_table->dev_list, node) {
|
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_allocate(struct opp_table *table)
|
||||||
{
|
{
|
||||||
struct dev_pm_opp *opp;
|
struct dev_pm_opp *opp;
|
||||||
int count, supply_size;
|
int supply_count, supply_size, icc_size;
|
||||||
|
|
||||||
/* Allocate space for at least one supply */
|
/* Allocate space for at least one supply */
|
||||||
count = table->regulator_count > 0 ? table->regulator_count : 1;
|
supply_count = table->regulator_count > 0 ? table->regulator_count : 1;
|
||||||
supply_size = sizeof(*opp->supplies) * count;
|
supply_size = sizeof(*opp->supplies) * supply_count;
|
||||||
|
icc_size = sizeof(*opp->bandwidth) * table->path_count;
|
||||||
|
|
||||||
/* allocate new OPP node and supplies structures */
|
/* 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)
|
if (!opp)
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
/* Put the supplies at the end of the OPP structure as an empty array */
|
/* Put the supplies at the end of the OPP structure as an empty array */
|
||||||
opp->supplies = (struct dev_pm_opp_supply *)(opp + 1);
|
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);
|
INIT_LIST_HEAD(&opp->node);
|
||||||
|
|
||||||
return opp;
|
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)
|
if (opp1->rate != opp2->rate)
|
||||||
return opp1->rate < opp2->rate ? -1 : 1;
|
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)
|
if (opp1->level != opp2->level)
|
||||||
return opp1->level < opp2->level ? -1 : 1;
|
return opp1->level < opp2->level ? -1 : 1;
|
||||||
return 0;
|
return 0;
|
||||||
|
|
114
drivers/opp/of.c
114
drivers/opp/of.c
|
@ -332,6 +332,62 @@ free_required_opps:
|
||||||
return ret;
|
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,
|
static bool _opp_is_supported(struct device *dev, struct opp_table *opp_table,
|
||||||
struct device_node *np)
|
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);
|
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,
|
static int _read_opp_key(struct dev_pm_opp *new_opp, struct device_node *np,
|
||||||
bool *rate_not_available)
|
bool *rate_not_available)
|
||||||
{
|
{
|
||||||
|
bool found = false;
|
||||||
u64 rate;
|
u64 rate;
|
||||||
int ret;
|
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.
|
* bit guaranteed in clk API.
|
||||||
*/
|
*/
|
||||||
new_opp->rate = (unsigned long)rate;
|
new_opp->rate = (unsigned long)rate;
|
||||||
|
found = true;
|
||||||
}
|
}
|
||||||
*rate_not_available = !!ret;
|
*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;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,6 +12,7 @@
|
||||||
#define __DRIVER_OPP_H__
|
#define __DRIVER_OPP_H__
|
||||||
|
|
||||||
#include <linux/device.h>
|
#include <linux/device.h>
|
||||||
|
#include <linux/interconnect.h>
|
||||||
#include <linux/kernel.h>
|
#include <linux/kernel.h>
|
||||||
#include <linux/kref.h>
|
#include <linux/kref.h>
|
||||||
#include <linux/list.h>
|
#include <linux/list.h>
|
||||||
|
@ -59,6 +60,7 @@ extern struct list_head opp_tables;
|
||||||
* @rate: Frequency in hertz
|
* @rate: Frequency in hertz
|
||||||
* @level: Performance level
|
* @level: Performance level
|
||||||
* @supplies: Power supplies voltage/current values
|
* @supplies: Power supplies voltage/current values
|
||||||
|
* @bandwidth: Interconnect bandwidth values
|
||||||
* @clock_latency_ns: Latency (in nanoseconds) of switching to this OPP's
|
* @clock_latency_ns: Latency (in nanoseconds) of switching to this OPP's
|
||||||
* frequency from any other OPP's frequency.
|
* frequency from any other OPP's frequency.
|
||||||
* @required_opps: List of OPPs that are required by this OPP.
|
* @required_opps: List of OPPs that are required by this OPP.
|
||||||
|
@ -81,6 +83,7 @@ struct dev_pm_opp {
|
||||||
unsigned int level;
|
unsigned int level;
|
||||||
|
|
||||||
struct dev_pm_opp_supply *supplies;
|
struct dev_pm_opp_supply *supplies;
|
||||||
|
struct dev_pm_opp_icc_bw *bandwidth;
|
||||||
|
|
||||||
unsigned long clock_latency_ns;
|
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
|
* @regulator_count: Number of power supply regulators. Its value can be -1
|
||||||
* (uninitialized), 0 (no opp-microvolt property) or > 0 (has opp-microvolt
|
* (uninitialized), 0 (no opp-microvolt property) or > 0 (has opp-microvolt
|
||||||
* property).
|
* property).
|
||||||
|
* @paths: Interconnect path handles
|
||||||
|
* @path_count: Number of interconnect paths
|
||||||
* @genpd_performance_state: Device's power domain support performance state.
|
* @genpd_performance_state: Device's power domain support performance state.
|
||||||
* @is_genpd: Marks if the OPP table belongs to a genpd.
|
* @is_genpd: Marks if the OPP table belongs to a genpd.
|
||||||
* @set_opp: Platform specific set_opp callback
|
* @set_opp: Platform specific set_opp callback
|
||||||
|
@ -189,6 +194,8 @@ struct opp_table {
|
||||||
struct clk *clk;
|
struct clk *clk;
|
||||||
struct regulator **regulators;
|
struct regulator **regulators;
|
||||||
int regulator_count;
|
int regulator_count;
|
||||||
|
struct icc_path **paths;
|
||||||
|
unsigned int path_count;
|
||||||
bool genpd_performance_state;
|
bool genpd_performance_state;
|
||||||
bool is_genpd;
|
bool is_genpd;
|
||||||
|
|
||||||
|
|
|
@ -41,6 +41,18 @@ struct dev_pm_opp_supply {
|
||||||
unsigned long u_amp;
|
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
|
* struct dev_pm_opp_info - OPP freq/voltage/current values
|
||||||
* @rate: Target clk rate in hz
|
* @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_of_get_opp_desc_node(struct device *dev);
|
||||||
struct device_node *dev_pm_opp_get_of_node(struct dev_pm_opp *opp);
|
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 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);
|
void dev_pm_opp_of_register_em(struct cpumask *cpus);
|
||||||
#else
|
#else
|
||||||
static inline int dev_pm_opp_of_add_table(struct device *dev)
|
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;
|
return -ENOTSUPP;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static inline int dev_pm_opp_of_find_icc_paths(struct device *dev, struct opp_table *opp_table)
|
||||||
|
{
|
||||||
|
return -ENOTSUPP;
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#endif /* __LINUX_OPP_H__ */
|
#endif /* __LINUX_OPP_H__ */
|
||||||
|
|
Loading…
Reference in New Issue