lkml.org 
[lkml]   [2019]   [Apr]   [23]   [last100]   RSS Feed
Views: [wrap][no wrap]   [headers]  [forward] 
 
Messages in this thread
Patch in this message
/
From
Subject[PATCH v2 3/5] OPP: Add support for parsing the interconnect bandwidth
Date
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.

Also add and export the dev_pm_opp_set_paths() and dev_pm_opp_put_paths()
helpers, to set (and release) an interconnect paths to a device. The
bandwidth of these paths will be updated when the OPPs are switched.

Signed-off-by: Georgi Djakov <georgi.djakov@linaro.org>
---
drivers/opp/core.c | 87 ++++++++++++++++++++++++++++++++++-
drivers/opp/of.c | 102 +++++++++++++++++++++++++++++++++++++++++
drivers/opp/opp.h | 9 ++++
include/linux/pm_opp.h | 14 ++++++
4 files changed, 210 insertions(+), 2 deletions(-)

diff --git a/drivers/opp/core.c b/drivers/opp/core.c
index 0420f7e8ad5b..97ee39ecdebd 100644
--- a/drivers/opp/core.c
+++ b/drivers/opp/core.c
@@ -19,6 +19,7 @@
#include <linux/slab.h>
#include <linux/device.h>
#include <linux/export.h>
+#include <linux/interconnect.h>
#include <linux/pm_domain.h>
#include <linux/regulator/consumer.h>

@@ -876,6 +877,8 @@ static struct opp_table *_allocate_opp_table(struct device *dev, int index)
ret);
}

+ _of_find_paths(opp_table, dev);
+
BLOCKING_INIT_NOTIFIER_HEAD(&opp_table->head);
INIT_LIST_HEAD(&opp_table->opp_list);
kref_init(&opp_table->kref);
@@ -1129,11 +1132,12 @@ 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 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;
+ icc_size = sizeof(*opp->bandwidth) * table->path_count;

/* allocate new OPP node and supplies structures */
opp = kzalloc(sizeof(*opp) + supply_size, GFP_KERNEL);
@@ -1141,7 +1145,8 @@ struct dev_pm_opp *_opp_allocate(struct opp_table *table)
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);
+ opp->bandwidth = (struct dev_pm_opp_icc_bw *)(opp + 1);
+ opp->supplies = (struct dev_pm_opp_supply *)(opp + icc_size + 1);
INIT_LIST_HEAD(&opp->node);

return opp;
@@ -1637,6 +1642,84 @@ void dev_pm_opp_put_clkname(struct opp_table *opp_table)
}
EXPORT_SYMBOL_GPL(dev_pm_opp_put_clkname);

+/**
+ * dev_pm_opp_set_paths() - Set interconnect path for a device
+ * @dev: Device for which interconnect path is being set.
+ *
+ * This must be called before any OPPs are initialized for the device.
+ */
+struct opp_table *dev_pm_opp_set_paths(struct device *dev)
+{
+ struct opp_table *opp_table;
+ int ret, i;
+
+ opp_table = dev_pm_opp_get_opp_table(dev);
+ if (!opp_table)
+ return ERR_PTR(-ENOMEM);
+
+ /* This should be called before OPPs are initialized */
+ if (WARN_ON(!list_empty(&opp_table->opp_list))) {
+ ret = -EBUSY;
+ goto err;
+ }
+
+ /* Another CPU that shares the OPP table has set the path */
+ if (opp_table->paths)
+ return opp_table;
+
+ opp_table->paths = kmalloc_array(opp_table->path_count,
+ sizeof(*opp_table->paths), GFP_KERNEL);
+
+ /* Find interconnect path(s) for the device */
+ for (i = 0; i < opp_table->path_count; i++) {
+ opp_table->paths[i] = of_icc_get_by_index(dev, i);
+ if (IS_ERR(opp_table->paths[i])) {
+ ret = PTR_ERR(opp_table->paths[i]);
+ if (ret != -EPROBE_DEFER)
+ dev_err(dev, "%s: Couldn't find path%d: %d\n",
+ __func__, i, ret);
+ goto err;
+ }
+ }
+
+ return opp_table;
+
+err:
+ dev_pm_opp_put_opp_table(opp_table);
+
+ return ERR_PTR(ret);
+}
+EXPORT_SYMBOL_GPL(dev_pm_opp_set_paths);
+
+/**
+ * dev_pm_opp_put_paths() - Release interconnect path resources
+ * @opp_table: OPP table returned from dev_pm_opp_set_paths().
+ */
+void dev_pm_opp_put_paths(struct opp_table *opp_table)
+{
+ int i;
+
+ if (!opp_table->paths) {
+ pr_err("%s: Doesn't have paths set\n", __func__);
+ return;
+ }
+
+ /* Make sure there are no concurrent readers while updating opp_table */
+ WARN_ON(!list_empty(&opp_table->opp_list));
+
+ for (i = opp_table->path_count - 1; i >= 0; i--)
+ icc_put(opp_table->paths[i]);
+
+ _free_set_opp_data(opp_table);
+
+ kfree(opp_table->paths);
+ opp_table->paths = NULL;
+ opp_table->path_count = 0;
+
+ dev_pm_opp_put_opp_table(opp_table);
+}
+EXPORT_SYMBOL_GPL(dev_pm_opp_put_paths);
+
/**
* dev_pm_opp_register_set_opp_helper() - Register custom set OPP helper
* @dev: Device for which the helper is getting registered.
diff --git a/drivers/opp/of.c b/drivers/opp/of.c
index c10c782d15aa..00af23280bc6 100644
--- a/drivers/opp/of.c
+++ b/drivers/opp/of.c
@@ -16,6 +16,7 @@
#include <linux/cpu.h>
#include <linux/errno.h>
#include <linux/device.h>
+#include <linux/interconnect.h>
#include <linux/of_device.h>
#include <linux/pm_domain.h>
#include <linux/slab.h>
@@ -363,6 +364,45 @@ static int _of_opp_alloc_required_opps(struct opp_table *opp_table,
return ret;
}

+int _of_find_paths(struct opp_table *opp_table, struct device *dev)
+{
+ struct device_node *np;
+ int ret, i, count, num_paths;
+
+ np = of_node_get(dev->of_node);
+ if (np) {
+ count = of_count_phandle_with_args(np, "interconnects",
+ "#interconnect-cells");
+ if (count % 2) {
+ dev_err(dev, "%s: Invalid interconnects values\n",
+ __func__);
+ ret = -EINVAL;
+ goto put_of_node;
+ }
+
+ num_paths = count / 2;
+ opp_table->paths = kcalloc(num_paths, sizeof(*opp_table->paths),
+ GFP_KERNEL);
+ if (!opp_table->paths) {
+ ret = -ENOMEM;
+ goto put_of_node;
+ }
+
+ for (i = 0; i < num_paths; i++)
+ opp_table->paths[i] = of_icc_get_by_index(dev, i);
+
+ opp_table->path_count = num_paths;
+ of_node_put(np);
+ }
+
+ return 0;
+
+put_of_node:
+ of_node_put(np);
+
+ return ret;
+}
+
static bool _opp_is_supported(struct device *dev, struct opp_table *opp_table,
struct device_node *np)
{
@@ -539,6 +579,64 @@ static int opp_parse_supplies(struct dev_pm_opp *opp, struct device *dev,
return ret;
}

+static int opp_parse_icc_bw(struct dev_pm_opp *opp, struct device *dev,
+ struct opp_table *opp_table)
+{
+ struct property *prop = NULL;
+ u32 *bandwidth;
+ char name[] = "bandwidth-MBps";
+ int count, i, j, ret;
+
+ /* Search for "bandwidth-MBps" */
+ prop = of_find_property(opp->np, name, NULL);
+
+ /* Missing property is not a problem */
+ if (!prop) {
+ dev_dbg(dev, "%s: Missing %s property\n", __func__, name);
+ return 0;
+ }
+
+ if (!prop->value) {
+ dev_dbg(dev, "%s: Missing %s value\n", __func__, name);
+ return -ENODATA;
+ }
+
+ /*
+ * Bandwidth consists of average and peak values like:
+ * bandwidth-MBps = <avg-MBps peak-MBps>
+ */
+ count = prop->length / sizeof(u32);
+ if (count % 2) {
+ dev_err(dev, "%s: Invalid %s values\n", __func__, name);
+ return -EINVAL;
+ }
+
+ if (opp_table->path_count != count / 2) {
+ dev_err(dev, "%s Mismatch between values and paths (%d %d)\n",
+ __func__, opp_table->path_count, count / 2);
+ return -EINVAL;
+ }
+
+ bandwidth = kmalloc_array(count, sizeof(*bandwidth), GFP_KERNEL);
+ if (!bandwidth)
+ return -ENOMEM;
+
+ ret = of_property_read_u32_array(opp->np, name, bandwidth, count);
+ if (ret) {
+ dev_err(dev, "%s: Error parsing %s: %d\n", __func__, name, ret);
+ goto free_bandwidth;
+ }
+ for (i = 0, j = 0; i < count; i++) {
+ opp->bandwidth[i].avg = MBps_to_icc(bandwidth[j++]);
+ opp->bandwidth[i].peak = MBps_to_icc(bandwidth[j++]);
+ }
+
+free_bandwidth:
+ kfree(bandwidth);
+
+ return ret;
+}
+
/**
* dev_pm_opp_of_remove_table() - Free OPP table entries created from static DT
* entries
@@ -635,6 +733,10 @@ static struct dev_pm_opp *_opp_add_static_v2(struct opp_table *opp_table,
if (opp_table->is_genpd)
new_opp->pstate = pm_genpd_opp_to_performance_state(dev, new_opp);

+ ret = opp_parse_icc_bw(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 */
diff --git a/drivers/opp/opp.h b/drivers/opp/opp.h
index 569b3525aa67..70a537f2dbd3 100644
--- a/drivers/opp/opp.h
+++ b/drivers/opp/opp.h
@@ -24,6 +24,7 @@

struct clk;
struct regulator;
+struct icc_path;

/* Lock to allow exclusive modification to the device and opp lists */
extern struct mutex opp_table_lock;
@@ -62,6 +63,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.
@@ -84,6 +86,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;

@@ -150,6 +153,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
@@ -194,6 +199,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;

@@ -228,12 +235,14 @@ void _of_clear_opp_table(struct opp_table *opp_table);
struct opp_table *_managed_opp(struct device *dev, int index);
void _of_opp_free_required_opps(struct opp_table *opp_table,
struct dev_pm_opp *opp);
+int _of_find_paths(struct opp_table *opp_table, struct device *dev);
#else
static inline void _of_init_opp_table(struct opp_table *opp_table, struct device *dev, int index) {}
static inline void _of_clear_opp_table(struct opp_table *opp_table) {}
static inline struct opp_table *_managed_opp(struct device *dev, int index) { return NULL; }
static inline void _of_opp_free_required_opps(struct opp_table *opp_table,
struct dev_pm_opp *opp) {}
+static inline int _of_find_paths(struct opp_table *opp_table, struct device *dev) { return 0; }
#endif

#ifdef CONFIG_DEBUG_FS
diff --git a/include/linux/pm_opp.h b/include/linux/pm_opp.h
index 24c757a32a7b..dabee09a92b8 100644
--- a/include/linux/pm_opp.h
+++ b/include/linux/pm_opp.h
@@ -43,6 +43,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
@@ -127,6 +139,8 @@ struct opp_table *dev_pm_opp_set_regulators(struct device *dev, const char * con
void dev_pm_opp_put_regulators(struct opp_table *opp_table);
struct opp_table *dev_pm_opp_set_clkname(struct device *dev, const char * name);
void dev_pm_opp_put_clkname(struct opp_table *opp_table);
+struct opp_table *dev_pm_opp_set_paths(struct device *dev);
+void dev_pm_opp_put_paths(struct opp_table *opp_table);
struct opp_table *dev_pm_opp_register_set_opp_helper(struct device *dev, int (*set_opp)(struct dev_pm_set_opp_data *data));
void dev_pm_opp_unregister_set_opp_helper(struct opp_table *opp_table);
struct opp_table *dev_pm_opp_set_genpd_virt_dev(struct device *dev, struct device *virt_dev, int index);
\
 
 \ /
  Last update: 2019-04-23 15:29    [W:0.150 / U:3.156 seconds]
©2003-2020 Jasper Spaans|hosted at Digital Ocean and TransIP|Read the blog|Advertise on this site