summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--drivers/opp/core.c28
-rw-r--r--drivers/opp/of.c114
-rw-r--r--drivers/opp/opp.h7
-rw-r--r--include/linux/pm_opp.h18
4 files changed, 162 insertions, 5 deletions
diff --git a/drivers/opp/core.c b/drivers/opp/core.c
index ce7e4103ec09..d19cc7970643 100644
--- a/drivers/opp/core.c
+++ b/drivers/opp/core.c
@@ -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;
diff --git a/drivers/opp/of.c b/drivers/opp/of.c
index 303d2207e0ff..0c55862f5624 100644
--- a/drivers/opp/of.c
+++ b/drivers/opp/of.c
@@ -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;
}
diff --git a/drivers/opp/opp.h b/drivers/opp/opp.h
index bcadb1e328a4..2b81ffef1ba4 100644
--- a/drivers/opp/opp.h
+++ b/drivers/opp/opp.h
@@ -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;
diff --git a/include/linux/pm_opp.h b/include/linux/pm_opp.h
index 747861816f4f..d5c4a329321d 100644
--- a/include/linux/pm_opp.h
+++ b/include/linux/pm_opp.h
@@ -42,6 +42,18 @@ struct dev_pm_opp_supply {
};
/**
+ * 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
* @supplies: Array of voltage/current values for all power supplies
@@ -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__ */