@@ -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,21 @@ 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;
+
+ rcu_read_unlock();
+
+ 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 +879,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);
@@ -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;
@@ -310,6 +310,45 @@ 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 (!new_opp->pd_perf_state) {
+ ret = -EINVAL;
+ dev_err(dev, "%s: OPP node can't have performance state as 0\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;
@@ -374,6 +413,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++;
@@ -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];