OPP: Level zero is valid
authorViresh Kumar <viresh.kumar@linaro.org>
Fri, 27 Oct 2023 07:10:04 +0000 (12:40 +0530)
committerViresh Kumar <viresh.kumar@linaro.org>
Tue, 28 Nov 2023 10:33:04 +0000 (16:03 +0530)
The level zero can be used by some OPPs to drop performance state vote
for the device. It is perfectly fine to allow the same.

_set_opp_level() considers it as an invalid value currently and returns
early.

In order to support this properly, initialize the level field with
U32_MAX, which denotes unused level field.

Reported-by: Stephan Gerhold <stephan.gerhold@kernkonzept.com>
Reviewed-by: Ulf Hansson <ulf.hansson@linaro.org>
Tested-by: Stephan Gerhold <stephan.gerhold@kernkonzept.com>
Signed-off-by: Viresh Kumar <viresh.kumar@linaro.org>
drivers/opp/core.c
drivers/opp/of.c
include/linux/pm_opp.h

index 84f345c69ea56e29b3a3bdcee2115aa60f3c99c4..f2e2aa07b43192f67074f14440cf9e295558aa82 100644 (file)
@@ -201,7 +201,7 @@ EXPORT_SYMBOL_GPL(dev_pm_opp_get_freq_indexed);
  * @opp:       opp for which level value has to be returned for
  *
  * Return: level read from device tree corresponding to the opp, else
- * return 0.
+ * return U32_MAX.
  */
 unsigned int dev_pm_opp_get_level(struct dev_pm_opp *opp)
 {
@@ -221,7 +221,7 @@ EXPORT_SYMBOL_GPL(dev_pm_opp_get_level);
  * @index:     index of the required opp
  *
  * Return: performance state read from device tree corresponding to the
- * required opp, else return 0.
+ * required opp, else return U32_MAX.
  */
 unsigned int dev_pm_opp_get_required_pstate(struct dev_pm_opp *opp,
                                            unsigned int index)
@@ -808,6 +808,14 @@ struct dev_pm_opp *dev_pm_opp_find_level_ceil(struct device *dev,
        struct dev_pm_opp *opp;
 
        opp = _find_key_ceil(dev, &temp, 0, true, _read_level, NULL);
+
+       /* False match */
+       if (temp == OPP_LEVEL_UNSET) {
+               dev_err(dev, "%s: OPP levels aren't available\n", __func__);
+               dev_pm_opp_put(opp);
+               return ERR_PTR(-ENODEV);
+       }
+
        *level = temp;
        return opp;
 }
@@ -1049,12 +1057,18 @@ static int _set_opp_bw(const struct opp_table *opp_table,
 static int _set_performance_state(struct device *dev, struct device *pd_dev,
                                  struct dev_pm_opp *opp, int i)
 {
-       unsigned int pstate = likely(opp) ? opp->required_opps[i]->level: 0;
+       unsigned int pstate = 0;
        int ret;
 
        if (!pd_dev)
                return 0;
 
+       if (likely(opp)) {
+               pstate = opp->required_opps[i]->level;
+               if (pstate == OPP_LEVEL_UNSET)
+                       return 0;
+       }
+
        ret = dev_pm_domain_set_performance_state(pd_dev, pstate);
        if (ret) {
                dev_err(dev, "Failed to set performance state of %s: %d (%d)\n",
@@ -1135,7 +1149,7 @@ static int _set_opp_level(struct device *dev, struct opp_table *opp_table,
        int ret = 0;
 
        if (opp) {
-               if (!opp->level)
+               if (opp->level == OPP_LEVEL_UNSET)
                        return 0;
 
                level = opp->level;
@@ -1867,6 +1881,8 @@ struct dev_pm_opp *_opp_allocate(struct opp_table *opp_table)
 
        INIT_LIST_HEAD(&opp->node);
 
+       opp->level = OPP_LEVEL_UNSET;
+
        return opp;
 }
 
index 81fa27599d58b98134d78d54f275b119fe19b996..85fad7ca0007a13ee10d4e8825845a1c73a0e75f 100644 (file)
@@ -1393,8 +1393,14 @@ int of_get_required_opp_performance_state(struct device_node *np, int index)
 
        opp = _find_opp_of_np(opp_table, required_np);
        if (opp) {
-               pstate = opp->level;
+               if (opp->level == OPP_LEVEL_UNSET) {
+                       pr_err("%s: OPP levels aren't available for %pOF\n",
+                              __func__, np);
+               } else {
+                       pstate = opp->level;
+               }
                dev_pm_opp_put(opp);
+
        }
 
        dev_pm_opp_put_opp_table(opp_table);
index ccd97bcef26943428a8683f9d9b2752c5171eedf..af53101a138397605f41885e452a5443707db699 100644 (file)
@@ -92,9 +92,12 @@ struct dev_pm_opp_config {
        struct device ***virt_devs;
 };
 
+#define OPP_LEVEL_UNSET                        U32_MAX
+
 /**
  * struct dev_pm_opp_data - The data to use to initialize an OPP.
- * @level: The performance level for the OPP.
+ * @level: The performance level for the OPP. Set level to OPP_LEVEL_UNSET if
+ * level field isn't used.
  * @freq: The clock rate in Hz for the OPP.
  * @u_volt: The voltage in uV for the OPP.
  */