diff mbox series

[1/4] clk: Aggregate power operation in clock controller

Message ID 20220805074935.1158098-2-jun.nie@linaro.org (mailing list archive)
State Not Applicable
Headers show
Series Support dynamic voltage frequency scaling inside clock controller | expand

Commit Message

Jun Nie Aug. 5, 2022, 7:49 a.m. UTC
Add power domain operation per clk frequency. Some hardware support
dynamic voltage frequency scaling in clock controller besides devices.
Which is not related to any clock consumer devices. While power domain
operation is operated per device in driver model. If they are voted per
clk, not per device, we need to aggregate them in clock framework,
then send request to power framework.

Signed-off-by: Jun Nie <jun.nie@linaro.org>
---
 drivers/clk/clk.c            | 212 ++++++++++++++++++++++++++++++++++-
 include/linux/clk-provider.h |  62 ++++++++++
 2 files changed, 272 insertions(+), 2 deletions(-)
diff mbox series

Patch

diff --git a/drivers/clk/clk.c b/drivers/clk/clk.c
index f00d4c1158d7..0ab79b9ebefd 100644
--- a/drivers/clk/clk.c
+++ b/drivers/clk/clk.c
@@ -18,6 +18,7 @@ 
 #include <linux/of.h>
 #include <linux/device.h>
 #include <linux/init.h>
+#include <linux/pm_domain.h>
 #include <linux/pm_runtime.h>
 #include <linux/sched.h>
 #include <linux/clkdev.h>
@@ -89,6 +90,7 @@  struct clk_core {
 	struct hlist_node	debug_node;
 #endif
 	struct kref		ref;
+	struct clk_power_data	*power;
 };
 
 #define CREATE_TRACE_POINTS
@@ -812,6 +814,161 @@  int clk_rate_exclusive_get(struct clk *clk)
 }
 EXPORT_SYMBOL_GPL(clk_rate_exclusive_get);
 
+static void clk_unvote_genpd(struct clk_core *core)
+{
+	struct clkpstate_node *ps_node = NULL;
+	struct clk_power_data *power = core->power;
+	unsigned int pstate = 0;
+
+	mutex_lock(power->genpd_lock);
+	/* Do not free the node. As performance states number is limited, and we
+	 * will re-visit it later.
+	 */
+	list_del_init(&power->genpd_list);
+	power->genpd_pstate = 0;
+
+	/* Find and set the highest pstate */
+	list_for_each_entry_reverse(ps_node, power->genpd_head, genpd_list) {
+		if (!list_empty(&ps_node->genpd_pstate_head)) {
+			pstate = ps_node->pstate;
+			break;
+		}
+	}
+
+	pr_debug("%s: clk %s unvote genpd set genpd perf state %d\n",
+		 __func__, core->name, pstate);
+	dev_pm_genpd_set_performance_state(*power->genpd_dev, pstate);
+
+	mutex_unlock(power->genpd_lock);
+}
+
+static int clk_vote_genpd(struct clk_core *core, unsigned long rate)
+{
+	struct clkpstate_node *new_ps_node, *ps_node, *pre_ps_node = NULL;
+	unsigned int cnt, pstate = 0;
+	struct list_head *insert_pos;
+	int ret = 0;
+	struct clk_power_data *power = core->power;
+	const struct genpdopp_table *tbl = power->genpdopp_table;
+
+	/* Find opp pstate for required rate */
+	for (cnt = 0; cnt < power->genpdopp_num; cnt++, tbl++) {
+		if (rate <= tbl->ceiling_rate) {
+			pstate = tbl->pstate;
+			break;
+		}
+	}
+
+	if (!pstate && cnt == power->genpdopp_num) {
+		pr_err("%s: clk %s rate %lu not supported by genpd\n", __func__,
+			core->name, rate);
+		return -EINVAL;
+	}
+
+	pr_debug("%s: clk %s votes perf state %d\n",
+		 __func__, core->name, pstate);
+	mutex_lock(power->genpd_lock);
+	if (list_empty(power->genpd_head)) {
+		insert_pos = power->genpd_head;
+		goto new_pstate_node;
+	}
+
+	/* If this clk power is already in some perf state */
+	if (!list_empty(&power->genpd_list)) {
+		if (pstate == power->genpd_pstate) {
+			mutex_unlock(power->genpd_lock);
+			return 0;
+		}
+		list_del_init(&power->genpd_list);
+	}
+
+	/* search the genpd pstate node that match pstate requirement */
+	list_for_each_entry(ps_node, power->genpd_head, genpd_list) {
+		if (ps_node->pstate == pstate) {
+			new_ps_node = ps_node;
+			list_add(&power->genpd_list,
+				 &new_ps_node->genpd_pstate_head);
+			goto linked_into_pstate;
+		}
+		if (ps_node->pstate > pstate) {
+			if (pre_ps_node != NULL)
+				insert_pos = &pre_ps_node->genpd_list;
+			else
+				insert_pos = power->genpd_head;
+			goto new_pstate_node;
+		}
+		pre_ps_node = ps_node;
+	}
+	/* Add new genpd pstate node in the end */
+	insert_pos = &pre_ps_node->genpd_list;
+
+new_pstate_node:
+	new_ps_node = kmalloc(sizeof(struct clkpstate_node), GFP_KERNEL);
+	if (new_ps_node == NULL) {
+		mutex_unlock(power->genpd_lock);
+		return -ENOMEM;
+	}
+
+	/* link this pstate node into genpd pstate link list */
+	INIT_LIST_HEAD(&new_ps_node->genpd_list);
+	INIT_LIST_HEAD(&new_ps_node->genpd_pstate_head);
+	new_ps_node->pstate = pstate;
+	list_add(&new_ps_node->genpd_list, insert_pos);
+	list_add(&power->genpd_list, &new_ps_node->genpd_pstate_head);
+
+	/* Find and set the highest pstate */
+	list_for_each_entry_reverse(ps_node, power->genpd_head, genpd_list) {
+		if (!list_empty(&ps_node->genpd_pstate_head)) {
+			pr_debug("%s: genpd set perf state %d for clk %s\n",
+				  __func__, pstate, core->name);
+			ret = dev_pm_genpd_set_performance_state(
+					*power->genpd_dev, ps_node->pstate);
+			if (ret) {
+				/* No need to free new_ps_node as it's empty */
+				mutex_unlock(power->genpd_lock);
+				pr_err("%s: fail to set genpd opp for clk %s\n",
+					__func__, core->name);
+				return ret;
+			}
+			break;
+		}
+	}
+
+linked_into_pstate:
+	power->genpd_pstate = pstate;
+	mutex_unlock(power->genpd_lock);
+	return ret;
+}
+
+static void clk_unvote_power(struct clk_core *core)
+{
+	struct clk_power_data *power = core->power;
+
+	if (!core->power)
+		return;
+
+	/* regulator added here in future */
+
+	if (power->genpd_dev)
+		clk_unvote_genpd(core);
+}
+
+static int clk_vote_power(struct clk_core *core, unsigned long rate)
+{
+	struct clk_power_data *power = core->power;
+	int ret = 0;
+
+	if (!core->power)
+		return 0;
+
+	/* regulator added here in future */
+
+	if (power->genpd_dev)
+		ret = clk_vote_genpd(core, rate);
+
+	return ret;
+}
+
 static void clk_core_unprepare(struct clk_core *core)
 {
 	lockdep_assert_held(&prepare_lock);
@@ -840,6 +997,8 @@  static void clk_core_unprepare(struct clk_core *core)
 	if (core->ops->unprepare)
 		core->ops->unprepare(core->hw);
 
+	clk_unvote_power(core);
+
 	clk_pm_runtime_put(core);
 
 	trace_clk_unprepare_complete(core);
@@ -887,6 +1046,10 @@  static int clk_core_prepare(struct clk_core *core)
 		if (ret)
 			return ret;
 
+		ret = clk_vote_power(core, core->rate);
+		if (ret)
+			return ret;
+
 		ret = clk_core_prepare(core->parent);
 		if (ret)
 			goto runtime_put;
@@ -2189,7 +2352,7 @@  static int clk_core_set_rate_nolock(struct clk_core *core,
 {
 	struct clk_core *top, *fail_clk;
 	unsigned long rate;
-	int ret = 0;
+	int ret = 0, post_set_power = 0;
 
 	if (!core)
 		return 0;
@@ -2223,10 +2386,21 @@  static int clk_core_set_rate_nolock(struct clk_core *core,
 		goto err;
 	}
 
+	if (rate > core->rate) {
+		ret = clk_vote_power(core, rate);
+		if (ret)
+			goto err;
+	} else {
+		post_set_power = 1;
+	}
+
 	/* change the rates */
 	clk_change_rate(top);
 
 	core->req_rate = req_rate;
+
+	if (post_set_power)
+		ret = clk_vote_power(core, rate);
 err:
 	clk_pm_runtime_put(core);
 
@@ -3905,7 +4079,8 @@  static void clk_core_free_parent_map(struct clk_core *core)
 static struct clk *
 __clk_register(struct device *dev, struct device_node *np, struct clk_hw *hw)
 {
-	int ret;
+	int ret, cnt;
+	unsigned long rate;
 	struct clk_core *core;
 	const struct clk_init_data *init = hw->init;
 
@@ -3946,6 +4121,38 @@  __clk_register(struct device *dev, struct device_node *np, struct clk_hw *hw)
 	core->min_rate = 0;
 	core->max_rate = ULONG_MAX;
 
+	if (init->power && init->power_magic == CLK_POWER_MAGIC) {
+		struct clk_power_data *power = init->power;
+		const struct genpdopp_table *ptable = power->genpdopp_table;
+
+		power->core = core;
+		if (power->genpd_dev) {
+			if (!power->genpd_lock || !power->genpd_head ||
+			   !power->genpdopp_table || !power->genpdopp_num) {
+				pr_err("%s: invalid power domain for clk %s\n",
+					__func__, core->name);
+				goto skip_clk_power;
+			}
+		}
+		for (cnt = 0; cnt < power->genpdopp_num - 1; cnt++) {
+			rate = ptable->ceiling_rate;
+			ptable++;
+			if (rate >= ptable->ceiling_rate) {
+				pr_err("%s: invalid asending rate for clk %s\n",
+					__func__, core->name);
+				ret = -EINVAL;
+				goto skip_clk_power;
+			}
+		}
+		core->power = kmalloc(sizeof(*power), GFP_KERNEL);
+		if (!core->power)
+			goto skip_clk_power;
+
+		memcpy(core->power, power, sizeof(*power));
+		INIT_LIST_HEAD(&core->power->genpd_list);
+	}
+
+skip_clk_power:
 	ret = clk_core_populate_parent_map(core, init);
 	if (ret)
 		goto fail_parents;
@@ -3978,6 +4185,7 @@  __clk_register(struct device *dev, struct device_node *np, struct clk_hw *hw)
 fail_create_clk:
 	clk_core_free_parent_map(core);
 fail_parents:
+	kfree(core->power);
 fail_ops:
 	kfree_const(core->name);
 fail_name:
diff --git a/include/linux/clk-provider.h b/include/linux/clk-provider.h
index c10dc4c659e2..bcf62fb0a6a1 100644
--- a/include/linux/clk-provider.h
+++ b/include/linux/clk-provider.h
@@ -268,12 +268,71 @@  struct clk_parent_data {
 	int			index;
 };
 
+/**
+ * struct genpdopp_table - opp pstate and clk rate mapping table
+ *
+ * @pstate: power domain performance state
+ * @ceiling_rate: the max clock rate this pstate supports
+ */
+struct genpdopp_table {
+	unsigned int	pstate;
+	unsigned long	ceiling_rate;
+};
+
+/**
+ * struct clkpstate_node - opp pstate node holds lists of clks that depends
+ *			on a specific performance state. The nodes should be
+ *			from low to high pstate lead by genpd_head.
+ *
+ * @genpd_list: list node that linked to a genpd list
+ * @genpd_pstate_head: list head that lead clks that depends on this pstate
+ * @pstate: power domain performance state
+ */
+struct clkpstate_node {
+	struct list_head	genpd_list;
+	struct list_head	genpd_pstate_head;
+	unsigned int		pstate;
+};
+
+/**
+ * struct clk_power_data - holds power data that's common to all clocks and is
+ * shared between the clock provider and the common clock framework.
+ *
+ * @genpd_list: genpd consumer node of this clk, to be into one of genpd pstate
+ *		consumer lists that lead by genpd_head when clk rate is set to
+ *		a genpd opp pstate.
+ * @genpd_head: list head that holds genpd performance states heads, where
+ *		genpd performance list heads are held. Those heads are holding
+ *		genpd consumers in different opp pstate.
+ * @genpd_lock: spin_lock that protect genpd list operation
+ * @genpd_dev: device that bind the power domain where clk is on. It is clock
+ *		controller device by default, or virtual device if there are
+ *		multiple power domain for controller device
+ * @genpdopp_table: genpd opp pstate and clk rate mapping table. The rate should
+		be listed from lowest to highest strictly in table.
+ * @genpdopp_num: genpd opp pstate table entry number
+ * @genpd_pstate: current genpd opp pstate this clk requires
+ */
+struct clk_power_data {
+	struct list_head		genpd_list;
+	struct list_head		*genpd_head;
+	struct mutex			*genpd_lock;
+	struct device			**genpd_dev;
+	const struct genpdopp_table	*genpdopp_table;
+	unsigned int			genpdopp_num;
+	unsigned int			genpd_pstate;
+	struct clk_core			*core;
+};
+
 /**
  * struct clk_init_data - holds init data that's common to all clocks and is
  * shared between the clock provider and the common clock framework.
  *
  * @name: clock name
  * @ops: operations this clock supports
+ * power: power data that this clock operates on
+ * @power_magic: magic number to indicate that power data is valid. To sanity
+ *	check for none NULL invalid power data case.
  * @parent_names: array of string names for all possible parents
  * @parent_data: array of parent data for all possible parents (when some
  *               parents are external to the clk controller)
@@ -282,9 +341,12 @@  struct clk_parent_data {
  * @num_parents: number of possible parents
  * @flags: framework-level hints and quirks
  */
+#define CLK_POWER_MAGIC 0x5c5c6969
 struct clk_init_data {
 	const char		*name;
+	int			power_magic;
 	const struct clk_ops	*ops;
+	struct clk_power_data	*power;
 	/* Only one of the following three should be assigned */
 	const char		* const *parent_names;
 	const struct clk_parent_data	*parent_data;