]> git.proxmox.com Git - mirror_ubuntu-bionic-kernel.git/blobdiff - drivers/opp/core.c
PM / OPP: Support updating performance state of device's power domain
[mirror_ubuntu-bionic-kernel.git] / drivers / opp / core.c
index 80c21207e48c92971e54508a0525e84968e5188c..0ce8069d68436bb53f3b0beec8ef29d3d6cb93c3 100644 (file)
@@ -19,6 +19,7 @@
 #include <linux/slab.h>
 #include <linux/device.h>
 #include <linux/export.h>
+#include <linux/pm_domain.h>
 #include <linux/regulator/consumer.h>
 
 #include "opp.h"
@@ -535,6 +536,44 @@ _generic_set_opp_clk_only(struct device *dev, struct clk *clk,
        return ret;
 }
 
+static inline int
+_generic_set_opp_domain(struct device *dev, struct clk *clk,
+                       unsigned long old_freq, unsigned long freq,
+                       unsigned int old_pstate, unsigned int new_pstate)
+{
+       int ret;
+
+       /* Scaling up? Scale domain performance state before frequency */
+       if (freq > old_freq) {
+               ret = dev_pm_genpd_set_performance_state(dev, new_pstate);
+               if (ret)
+                       return ret;
+       }
+
+       ret = _generic_set_opp_clk_only(dev, clk, old_freq, freq);
+       if (ret)
+               goto restore_domain_state;
+
+       /* Scaling down? Scale domain performance state after frequency */
+       if (freq < old_freq) {
+               ret = dev_pm_genpd_set_performance_state(dev, new_pstate);
+               if (ret)
+                       goto restore_freq;
+       }
+
+       return 0;
+
+restore_freq:
+       if (_generic_set_opp_clk_only(dev, clk, freq, old_freq))
+               dev_err(dev, "%s: failed to restore old-freq (%lu Hz)\n",
+                       __func__, old_freq);
+restore_domain_state:
+       if (freq > old_freq)
+               dev_pm_genpd_set_performance_state(dev, old_pstate);
+
+       return ret;
+}
+
 static int _generic_set_opp_regulator(const struct opp_table *opp_table,
                                      struct device *dev,
                                      unsigned long old_freq,
@@ -653,7 +692,16 @@ int dev_pm_opp_set_rate(struct device *dev, unsigned long target_freq)
 
        /* Only frequency scaling */
        if (!opp_table->regulators) {
-               ret = _generic_set_opp_clk_only(dev, clk, old_freq, freq);
+               /*
+                * We don't support devices with both regulator and
+                * domain performance-state for now.
+                */
+               if (opp_table->genpd_performance_state)
+                       ret = _generic_set_opp_domain(dev, clk, old_freq, freq,
+                                                     IS_ERR(old_opp) ? 0 : old_opp->pstate,
+                                                     opp->pstate);
+               else
+                       ret = _generic_set_opp_clk_only(dev, clk, old_freq, freq);
        } else if (!opp_table->set_opp) {
                ret = _generic_set_opp_regulator(opp_table, dev, old_freq, freq,
                                                 IS_ERR(old_opp) ? NULL : old_opp->supplies,
@@ -1706,6 +1754,13 @@ void _dev_pm_opp_remove_table(struct opp_table *opp_table, struct device *dev,
                        if (remove_all || !opp->dynamic)
                                dev_pm_opp_put(opp);
                }
+
+               /*
+                * The OPP table is getting removed, drop the performance state
+                * constraints.
+                */
+               if (opp_table->genpd_performance_state)
+                       dev_pm_genpd_set_performance_state(dev, 0);
        } else {
                _remove_opp_dev(_find_opp_dev(dev, opp_table), opp_table);
        }