opp: Add support for parsing interconnect bandwidth
authorGeorgi Djakov <georgi.djakov@linaro.org>
Tue, 12 May 2020 12:53:21 +0000 (15:53 +0300)
committerViresh Kumar <viresh.kumar@linaro.org>
Fri, 29 May 2020 04:45:08 +0000 (10:15 +0530)
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>
drivers/opp/core.c
drivers/opp/of.c
drivers/opp/opp.h
include/linux/pm_opp.h

index ce7e410..d19cc79 100644 (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;
index 303d220..0c55862 100644 (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;
 }
index bcadb1e..2b81ffe 100644 (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;
 
index 7478618..d5c4a32 100644 (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__ */