lkml.org 
[lkml]   [2020]   [Jul]   [30]   [last100]   RSS Feed
Views: [wrap][no wrap]   [headers]  [forward] 
 
Messages in this thread
Patch in this message
/
From
Subject[RFC PATCH 3/3] opp: Power on (virtual) power domains managed by the OPP core
Date
dev_pm_opp_attach_genpd() allows attaching an arbitrary number of
power domains to an OPP table. In that case, the genpd core will
create a virtual device for each of the power domains.

At the moment, the OPP core only calls
dev_pm_genpd_set_performance_state() on these virtual devices.
It does not attempt to power on the power domains. Therefore
the required power domain might never get turned on.

So far, dev_pm_opp_attach_genpd() is only used in qcom-cpufreq-nvmem.c
to attach the CPR power domain to the CPU OPP table. The CPR driver
does not check if it was actually powered on so this did not cause
any problems. However, other drivers (e.g. rpmpd) might ignore the
performance state until the power domain is actually powered on.

Since these virtual devices are managed exclusively by the OPP core,
I would say that it should also be responsible to ensure they are
enabled. A similar approach is already used for regulators, see
commit 8d45719caaf5 ("opp: core: add regulators enable and disable").

This commit implements similar functionality for the virtual genpd
devices managed by the OPP core. The power domains are turned on
the first time dev_pm_opp_set_rate() is called. They are turned off
again when dev_pm_opp_set_rate(dev, 0) is called.

Signed-off-by: Stephan Gerhold <stephan@gerhold.net>
---
Related discussion: https://lore.kernel.org/linux-arm-msm/20200426123140.GA190483@gerhold.net/

There would be also other ways to implement this, e.g. device links,
assuming that the device using the OPP table also makes use of runtime PM.
My first thought was that it would be most consistent to handle this like
regulators, bandwidth votes etc. RFC :)
---
drivers/opp/core.c | 55 ++++++++++++++++++++++++++++++++++++++++++++++
drivers/opp/opp.h | 1 +
2 files changed, 56 insertions(+)

diff --git a/drivers/opp/core.c b/drivers/opp/core.c
index f93f551c911e..66ecffe12f01 100644
--- a/drivers/opp/core.c
+++ b/drivers/opp/core.c
@@ -17,6 +17,7 @@
#include <linux/device.h>
#include <linux/export.h>
#include <linux/pm_domain.h>
+#include <linux/pm_runtime.h>
#include <linux/regulator/consumer.h>

#include "opp.h"
@@ -796,6 +797,26 @@ static int _set_required_opp(struct device *dev, struct device *pd_dev,
return ret;
}

+static int _enable_required_opp(struct device *dev, struct device *pd_dev,
+ bool on)
+{
+ int ret;
+
+ if (on) {
+ ret = pm_runtime_get_sync(pd_dev);
+ if (ret < 0) {
+ pm_runtime_put_noidle(pd_dev);
+ dev_err(dev, "Failed to enable %s: %d\n",
+ dev_name(pd_dev), ret);
+ return ret;
+ }
+ } else {
+ pm_runtime_put(pd_dev);
+ }
+
+ return 0;
+}
+
/* This is only called for PM domain for now */
static int _set_required_opps(struct device *dev,
struct opp_table *opp_table,
@@ -803,6 +824,8 @@ static int _set_required_opps(struct device *dev,
{
struct opp_table **required_opp_tables = opp_table->required_opp_tables;
struct device **genpd_virt_devs = opp_table->genpd_virt_devs;
+ bool power_on = opp != NULL;
+ bool already_enabled = power_on == opp_table->genpd_virt_enabled;
struct device *pd_dev;
int i, ret = 0;

@@ -829,6 +852,20 @@ static int _set_required_opps(struct device *dev,
ret = _set_required_opp(dev, pd_dev, opp, i);
if (ret)
break;
+
+ if (likely(already_enabled))
+ continue;
+
+ ret = _enable_required_opp(dev, pd_dev, power_on);
+ if (ret)
+ break;
+ }
+
+ if (ret && !already_enabled) {
+ /* Rollback (skip current since it failed) */
+ for (i--; i >= 0; i--)
+ _enable_required_opp(dev, genpd_virt_devs[i],
+ !power_on);
}
} else {
/* Scaling down? Set required OPPs in reverse order */
@@ -838,8 +875,26 @@ static int _set_required_opps(struct device *dev,
ret = _set_required_opp(dev, pd_dev, opp, i);
if (ret)
break;
+
+ if (likely(already_enabled))
+ continue;
+
+ ret = _enable_required_opp(dev, pd_dev, power_on);
+ if (ret)
+ break;
+ }
+
+ if (ret && !already_enabled) {
+ /* Rollback (skip current since it failed) */
+ for (i++; i < opp_table->required_opp_count; i++)
+ _enable_required_opp(dev, genpd_virt_devs[i],
+ !power_on);
}
}
+
+ if (ret == 0 && !already_enabled)
+ opp_table->genpd_virt_enabled = power_on;
+
mutex_unlock(&opp_table->genpd_virt_dev_lock);

return ret;
diff --git a/drivers/opp/opp.h b/drivers/opp/opp.h
index e51646ff279e..01ad9e136cc8 100644
--- a/drivers/opp/opp.h
+++ b/drivers/opp/opp.h
@@ -188,6 +188,7 @@ struct opp_table {
struct device **genpd_virt_devs;
struct opp_table **required_opp_tables;
unsigned int required_opp_count;
+ bool genpd_virt_enabled;

unsigned int *supported_hw;
unsigned int supported_hw_count;
--
2.27.0
\
 
 \ /
  Last update: 2020-07-30 10:02    [W:0.139 / U:1.140 seconds]
©2003-2020 Jasper Spaans|hosted at Digital Ocean and TransIP|Read the blog|Advertise on this site