There could be single power domain or multiple power domains associated
with a clock controller. Add powerdomain_class support which would help
vote/unvote for any power domain performance state for a clock frequency
to the genpd framework.
A clock frequency request from a consumer would look for the corresponding
performance corner and thus would aggregate and request the desired
performance state to genpd.

Signed-off-by: Taniya Das <t...@codeaurora.org>
---
 drivers/clk/qcom/Makefile |   1 +
 drivers/clk/qcom/clk-pd.c | 193 ++++++++++++++++++++++++++++++++++++++++++++++
 drivers/clk/qcom/clk-pd.h |  55 +++++++++++++
 3 files changed, 249 insertions(+)
 create mode 100644 drivers/clk/qcom/clk-pd.c
 create mode 100644 drivers/clk/qcom/clk-pd.h

diff --git a/drivers/clk/qcom/Makefile b/drivers/clk/qcom/Makefile
index 599ab91..336d4da 100644
--- a/drivers/clk/qcom/Makefile
+++ b/drivers/clk/qcom/Makefile
@@ -12,6 +12,7 @@ clk-qcom-y += clk-regmap-divider.o
 clk-qcom-y += clk-regmap-mux.o
 clk-qcom-y += clk-regmap-mux-div.o
 clk-qcom-y += reset.o
+clk-qcom-y += clk-pd.o
 clk-qcom-$(CONFIG_QCOM_GDSC) += gdsc.o
 
 # Keep alphabetically sorted by config
diff --git a/drivers/clk/qcom/clk-pd.c b/drivers/clk/qcom/clk-pd.c
new file mode 100644
index 0000000..d1f9df3
--- /dev/null
+++ b/drivers/clk/qcom/clk-pd.c
@@ -0,0 +1,193 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2018, The Linux Foundation. All rights reserved.
+ */
+
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/pm_domain.h>
+#include <linux/pm_runtime.h>
+#include <linux/slab.h>
+
+#include "clk-pd.h"
+#include "clk-regmap.h"
+
+struct clk_powerdomain {
+       struct list_head list;
+       struct clk_powerdomain_class *pd;
+};
+
+static LIST_HEAD(clk_pd_list);
+
+/* Find the corner required for a given clock rate */
+static int find_rate_to_corner(struct clk_regmap *rclk, unsigned long rate)
+{
+       int corner;
+
+       for (corner = 0; corner < rclk->pd->num_corners; corner++)
+               if (rate <= rclk->rate_max[corner])
+                       break;
+
+       if (corner == rclk->pd->num_corners) {
+               pr_debug("Rate %lu for %s is > than highest Fmax\n", rate,
+                        rclk->hw.init->name);
+               return -EINVAL;
+       }
+
+       return corner;
+}
+
+static int pd_update_corner_state(struct clk_powerdomain_class *pd)
+{
+       int corner, ret, *state = pd->corner, i;
+       int cur_corner = pd->cur_corner, max_corner = pd->num_corners - 1;
+
+       /* Aggregate the corner */
+       for (corner = max_corner; corner > 0; corner--) {
+               if (pd->corner_votes[corner])
+                       break;
+       }
+
+       if (corner == cur_corner)
+               return 0;
+
+       pr_debug("Set performance state to genpd(%s) for state %d, cur_corner 
%d, num_corner %d\n",
+                pd->pd_name, state[corner], cur_corner, pd->num_corners);
+
+       for (i = 0; i < pd->num_pd; i++) {
+               ret = dev_pm_genpd_set_performance_state(pd->powerdomain_dev[i],
+                                                        state[corner]);
+               if (ret)
+                       return ret;
+
+               if (cur_corner == 0 || cur_corner == pd->num_corners) {
+                       pd->links[i] = device_link_add(pd->dev,
+                                       pd->powerdomain_dev[i],
+                                       DL_FLAG_STATELESS |
+                                       DL_FLAG_PM_RUNTIME |
+                                       DL_FLAG_RPM_ACTIVE);
+                       if (!pd->links[i])
+                               pr_err("Links for %d not created\n", i);
+               }
+
+               if (corner == 0)
+                       device_link_del(pd->links[i]);
+       }
+
+       pd->cur_corner = corner;
+
+       return 0;
+}
+
+/* call from prepare & set rate */
+int clk_power_domain_vote_rate(struct clk_regmap *rclk,
+                               unsigned long rate)
+{
+       int corner;
+
+       if (!rclk->pd)
+               return 0;
+
+       corner = find_rate_to_corner(rclk, rate);
+       if (corner < 0)
+               return corner;
+
+       mutex_lock(&rclk->pd->lock);
+
+       rclk->pd->corner_votes[corner]++;
+
+       /* update the corner to power domain */
+       if (pd_update_corner_state(rclk->pd) < 0)
+               rclk->pd->corner_votes[corner]--;
+
+       pr_debug("pd(%s) prepare corner_votes_count %d, corner %d\n",
+                rclk->pd->pd_name, rclk->pd->corner_votes[corner],
+                corner);
+
+       mutex_unlock(&rclk->pd->lock);
+
+       return 0;
+}
+EXPORT_SYMBOL_GPL(clk_power_domain_vote_rate);
+
+/* call from unprepare & set rate */
+void clk_power_domain_unvote_rate(struct clk_regmap *rclk,
+                                  unsigned long rate)
+{
+       int corner;
+
+       if (!rclk->pd)
+               return;
+
+       corner = find_rate_to_corner(rclk, rate);
+       if (corner < 0)
+               return;
+
+       if (WARN(!rclk->pd->corner_votes[corner],
+                "Reference counts are incorrect for %s corner %d\n",
+                rclk->pd->pd_name, corner))
+               return;
+
+       mutex_lock(&rclk->pd->lock);
+
+       rclk->pd->corner_votes[corner]--;
+
+       if (pd_update_corner_state(rclk->pd) < 0)
+               rclk->pd->corner_votes[corner]++;
+
+       pr_debug("pd(%s) unprepare corner_votes_count %d, corner %d\n",
+                rclk->pd->pd_name, rclk->pd->corner_votes[corner],
+                corner);
+
+       mutex_unlock(&rclk->pd->lock);
+}
+EXPORT_SYMBOL_GPL(clk_power_domain_unvote_rate);
+
+int clk_power_domain_class_init(struct device *dev,
+                               struct clk_powerdomain_class *pd)
+{
+       struct clk_powerdomain *pwrd;
+       int i, num_domains;
+
+       if (!pd) {
+               pr_debug("pd not defined\n");
+               return 0;
+       }
+
+       /* Deal only with devices using multiple PM domains. */
+       num_domains = of_count_phandle_with_args(dev->of_node, "power-domains",
+                                                "#power-domain-cells");
+
+       list_for_each_entry(pwrd, &clk_pd_list, list) {
+               if (pwrd->pd == pd)
+                       return 0;
+       }
+
+       pr_debug("Voting for pd_class %s\n", pd->pd_name);
+
+       if (num_domains == 1) {
+               pd->powerdomain_dev[0] = dev;
+       } else {
+               for (i = 0; i < pd->num_pd; i++) {
+                       int index = pd->pd_index[i];
+
+                       pd->powerdomain_dev[i] = genpd_dev_pm_attach_by_id(dev,
+                                                                       index);
+               }
+       }
+
+       pwrd = kmalloc(sizeof(*pwrd), GFP_KERNEL);
+       if (!pwrd)
+               return -ENOMEM;
+
+       pwrd->pd = pd;
+       list_add_tail(&pwrd->list, &clk_pd_list);
+
+       pd->dev = dev;
+
+       return 0;
+}
+EXPORT_SYMBOL_GPL(clk_power_domain_class_init);
+
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/clk/qcom/clk-pd.h b/drivers/clk/qcom/clk-pd.h
new file mode 100644
index 0000000..addde4f
--- /dev/null
+++ b/drivers/clk/qcom/clk-pd.h
@@ -0,0 +1,55 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Copyright (c) 2018, The Linux Foundation. All rights reserved. */
+
+struct clk_regmap;
+
+/**
+ * struct clk_powerdomain_class - Power Domain scaling class
+ * @pd_name:           name of the power domain class
+ * @dev:               clock controller device
+ * @powerdomain_dev:   array of power domain devices
+ * @links:             array of power domain devices linked
+ * @num_pd:            size of power domain devices array
+ * @pd_index:          array of power domain index which would
+ *                     be used to attach the power domain using
+ *                     genpd_dev_pm_attach_by_id(dev, index)
+ * @corner:            sorted 2D array of legal corner settings,
+                       indexed by the corner
+ * @corner_votes:      array of votes for each corner
+ * @num_corner:                specifies the size of corner_votes array
+ * @cur_corner:                current set power domain corner
+ * @lock:              lock to protect this struct
+ */
+struct clk_powerdomain_class {
+       const char *pd_name;
+       struct device *dev;
+       struct device **powerdomain_dev;
+       struct device_link **links;
+       int num_pd;
+       int *pd_index;
+       int *corner;
+       int *corner_votes;
+       int num_corners;
+       unsigned int cur_corner;
+       /* Protect this struct */
+       struct mutex lock;
+};
+
+#define CLK_POWERDOMAIN_INIT(_name, _num_corners, _num_pd, _corner)    \
+       struct clk_powerdomain_class _name = {                          \
+               .pd_name = #_name,                                      \
+               .powerdomain_dev = (struct device *([_num_pd])) {},     \
+               .links = (struct device_link *([_num_pd])) {},          \
+               .num_pd = _num_pd,                                      \
+               .pd_index = (int [_num_pd]) {},                         \
+               .corner_votes = (int [_num_corners]) {},                \
+               .num_corners = _num_corners,                            \
+               .corner = _corner,                                      \
+               .cur_corner = _num_corners,                             \
+               .lock = __MUTEX_INITIALIZER(_name.lock)                 \
+       }
+
+int clk_power_domain_class_init(struct device *dev,
+                               struct clk_powerdomain_class *pd);
+int clk_power_domain_vote_rate(struct clk_regmap *rclk, unsigned long rate);
+void clk_power_domain_unvote_rate(struct clk_regmap *rclk, unsigned long rate);
-- 
Qualcomm INDIA, on behalf of Qualcomm Innovation Center, Inc.is a member
of the Code Aurora Forum, hosted by the  Linux Foundation.

Reply via email to