[PATCH V3 7/7] PM / OPP: Add support to parse domain-performance-state

From: Viresh Kumar
Date: Fri Feb 24 2017 - 04:35:43 EST


This patch allows the OPP core to parse the "domain-performance-state"
property in the OPP nodes. The nodes are allowed to have the
"domain-performance-state" property, only if the device node contains a
"power-domains" property. The OPP nodes aren't allowed to contain the
property partially, i.e. Either all OPP nodes in the OPP table have the
"domain-performance-state" property or none of them have it.

Signed-off-by: Viresh Kumar <viresh.kumar@xxxxxxxxxx>
Tested-by: Rajendra Nayak <rnayak@xxxxxxxxxxxxxx>
---
drivers/base/power/opp/core.c | 73 ++++++++++++++++++++++++++++++++++++++++
drivers/base/power/opp/debugfs.c | 4 +++
drivers/base/power/opp/of.c | 37 ++++++++++++++++++++
drivers/base/power/opp/opp.h | 12 +++++++
4 files changed, 126 insertions(+)

diff --git a/drivers/base/power/opp/core.c b/drivers/base/power/opp/core.c
index 91ec3232d630..211551f377e9 100644
--- a/drivers/base/power/opp/core.c
+++ b/drivers/base/power/opp/core.c
@@ -542,6 +542,63 @@ _generic_set_opp_clk_only(struct device *dev, struct clk *clk,
return ret;
}

+static int _update_pm_qos_request(struct device *dev,
+ struct dev_pm_qos_request *req,
+ unsigned int perf)
+{
+ int ret;
+
+ if (likely(dev_pm_qos_request_active(req)))
+ ret = dev_pm_qos_update_request(req, perf);
+ else
+ ret = dev_pm_qos_add_request(dev, req, DEV_PM_QOS_PERFORMANCE,
+ perf);
+
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static int _generic_set_opp_pd(struct device *dev, struct clk *clk,
+ struct dev_pm_qos_request *req,
+ unsigned long old_freq, unsigned long freq,
+ unsigned int old_perf, unsigned int new_perf)
+{
+ int ret;
+
+ /* Scaling up? Scale voltage before frequency */
+ if (freq > old_freq) {
+ ret = _update_pm_qos_request(dev, req, new_perf);
+ if (ret)
+ return ret;
+ }
+
+ /* Change frequency */
+ ret = _generic_set_opp_clk_only(dev, clk, old_freq, freq);
+ if (ret)
+ goto restore_perf;
+
+ /* Scaling down? Scale voltage after frequency */
+ if (freq < old_freq) {
+ ret = _update_pm_qos_request(dev, req, new_perf);
+ 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_perf:
+ if (old_perf)
+ _update_pm_qos_request(dev, req, old_perf);
+
+ return ret;
+}
+
static int _generic_set_opp(struct dev_pm_set_opp_data *data)
{
struct dev_pm_opp_supply *old_supply = data->old_opp.supplies;
@@ -662,6 +719,19 @@ int dev_pm_opp_set_rate(struct device *dev, unsigned long target_freq)

regulators = opp_table->regulators;

+ /* Has power domains performance states */
+ if (opp_table->has_pd_perf_states) {
+ unsigned int old_perf = 0, new_perf;
+ struct dev_pm_qos_request *req = &opp_table->qos_request;
+
+ new_perf = opp->pd_perf_state;
+ if (!IS_ERR(old_opp))
+ old_perf = old_opp->pd_perf_state;
+
+ return _generic_set_opp_pd(dev, clk, req, old_freq, freq,
+ old_perf, new_perf);
+ }
+
/* Only frequency scaling */
if (!regulators) {
ret = _generic_set_opp_clk_only(dev, clk, old_freq, freq);
@@ -807,6 +877,9 @@ 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;

+ if (dev_pm_qos_request_active(&opp_table->qos_request))
+ dev_pm_qos_remove_request(&opp_table->qos_request);
+
/* Release clk */
if (!IS_ERR(opp_table->clk))
clk_put(opp_table->clk);
diff --git a/drivers/base/power/opp/debugfs.c b/drivers/base/power/opp/debugfs.c
index 95f433db4ac7..264958ab3de9 100644
--- a/drivers/base/power/opp/debugfs.c
+++ b/drivers/base/power/opp/debugfs.c
@@ -104,6 +104,10 @@ int opp_debug_create_one(struct dev_pm_opp *opp, struct opp_table *opp_table)
if (!debugfs_create_ulong("rate_hz", S_IRUGO, d, &opp->rate))
return -ENOMEM;

+ if (!debugfs_create_u32("power_domain_perf_state", S_IRUGO, d,
+ &opp->pd_perf_state))
+ return -ENOMEM;
+
if (!opp_debug_create_supplies(opp, opp_table, d))
return -ENOMEM;

diff --git a/drivers/base/power/opp/of.c b/drivers/base/power/opp/of.c
index 779428676f63..e3b5f10e7f25 100644
--- a/drivers/base/power/opp/of.c
+++ b/drivers/base/power/opp/of.c
@@ -311,6 +311,38 @@ static int _opp_add_static_v2(struct opp_table *opp_table, struct device *dev,
if (!of_property_read_u32(np, "clock-latency-ns", &val))
new_opp->clock_latency_ns = val;

+ /*
+ * Make sure that all information is present around domain power states
+ * and nothing is left out.
+ */
+ if (!of_property_read_u32(np, "domain-performance-state",
+ &new_opp->pd_perf_state)) {
+ if (!opp_table->has_pd) {
+ ret = -EINVAL;
+ dev_err(dev, "%s: OPP node can't have performance state as device doesn't have power-domain\n",
+ __func__);
+ goto free_opp;
+ }
+
+ if (opp_table->has_pd_perf_states == -1) {
+ opp_table->has_pd_perf_states = 1;
+ } else if (!opp_table->has_pd_perf_states) {
+ ret = -EINVAL;
+ dev_err(dev, "%s: Not all OPP nodes have performance state\n",
+ __func__);
+ goto free_opp;
+ }
+ } else {
+ if (opp_table->has_pd_perf_states == -1) {
+ opp_table->has_pd_perf_states = 0;
+ } else if (opp_table->has_pd_perf_states) {
+ ret = -EINVAL;
+ dev_err(dev, "%s: Not all OPP nodes have performance state\n",
+ __func__);
+ goto free_opp;
+ }
+ }
+
ret = opp_parse_supplies(new_opp, dev, opp_table);
if (ret)
goto free_opp;
@@ -375,6 +407,11 @@ static int _of_add_opp_table_v2(struct device *dev, struct device_node *opp_np)
if (!opp_table)
return -ENOMEM;

+ if (of_find_property(dev->of_node, "power-domains", NULL)) {
+ opp_table->has_pd = true;
+ opp_table->has_pd_perf_states = -1;
+ }
+
/* We have opp-table node now, iterate over it and add OPPs */
for_each_available_child_of_node(opp_np, np) {
count++;
diff --git a/drivers/base/power/opp/opp.h b/drivers/base/power/opp/opp.h
index 166eef990599..41a2c0a67031 100644
--- a/drivers/base/power/opp/opp.h
+++ b/drivers/base/power/opp/opp.h
@@ -20,6 +20,7 @@
#include <linux/list.h>
#include <linux/limits.h>
#include <linux/pm_opp.h>
+#include <linux/pm_qos.h>
#include <linux/notifier.h>

struct clk;
@@ -58,6 +59,7 @@ extern struct list_head opp_tables;
* @dynamic: not-created from static DT entries.
* @turbo: true if turbo (boost) OPP
* @suspend: true if suspend OPP
+ * @pd_perf_state: Performance state of power domain
* @rate: Frequency in hertz
* @supplies: Power supplies voltage/current values
* @clock_latency_ns: Latency (in nanoseconds) of switching to this OPP's
@@ -76,6 +78,7 @@ struct dev_pm_opp {
bool dynamic;
bool turbo;
bool suspend;
+ unsigned int pd_perf_state;
unsigned long rate;

struct dev_pm_opp_supply *supplies;
@@ -137,6 +140,11 @@ enum opp_table_access {
* @regulator_count: Number of power supply regulators
* @set_opp: Platform specific set_opp callback
* @set_opp_data: Data to be passed to set_opp callback
+ * @has_pd: True if the device node contains power-domain property
+ * @has_pd_perf_states: Can have value of 0, 1 or -1. -1 means uninitialized
+ * state, 0 means that OPP nodes don't have perf states and 1 means that OPP
+ * nodes have perf states.
+ * @qos_request: Qos request.
* @dentry: debugfs dentry pointer of the real device directory (not links).
* @dentry_name: Name of the real dentry.
*
@@ -174,6 +182,10 @@ struct opp_table {
int (*set_opp)(struct dev_pm_set_opp_data *data);
struct dev_pm_set_opp_data *set_opp_data;

+ bool has_pd;
+ int has_pd_perf_states;
+ struct dev_pm_qos_request qos_request;
+
#ifdef CONFIG_DEBUG_FS
struct dentry *dentry;
char dentry_name[NAME_MAX];
--
2.7.1.410.g6faf27b