[PATCH v2 4/6] clk: add coordinated clk changes support

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



This adds a new clk_op, pre_rate_req. It allows clks to setup an
intermediate state when clk rates are changed. One use case for this
is when a clk needs to switch to a safe parent when its PLL ancestor
changes rates. This is needed when a PLL cannot guarantee that it will
not exceed the new rate before it locks. The set_rate, set_parent, and
set_rate_and_parent callbacks are used with the pre_rate_req callback.

Signed-off-by: Derek Basehore <dbasehore@xxxxxxxxxxxx>
---
 drivers/clk/clk.c            | 207 ++++++++++++++++++++++++++++++++---
 include/linux/clk-provider.h |  10 ++
 2 files changed, 200 insertions(+), 17 deletions(-)

diff --git a/drivers/clk/clk.c b/drivers/clk/clk.c
index 1637dc262884..b86940ca3c81 100644
--- a/drivers/clk/clk.c
+++ b/drivers/clk/clk.c
@@ -59,6 +59,7 @@ struct clk_core {
 	unsigned long		rate;
 	unsigned long		req_rate;
 	struct clk_change	change;
+	struct clk_change	pre_change;
 	struct clk_core		*new_child;
 	unsigned long		flags;
 	bool			orphan;
@@ -1920,6 +1921,141 @@ static struct clk_core *clk_propagate_rate_change(struct clk_core *core,
 	return fail_clk;
 }
 
+static void clk_add_change(struct list_head *changes,
+			       struct clk_change *change,
+			       struct clk_core *parent,
+			       unsigned long rate)
+{
+	change->parent = parent;
+	change->rate = rate;
+	list_add(&change->change_list, changes);
+}
+
+static int clk_prepare_pre_changes(struct list_head *pre_changes,
+				   struct list_head *post_changes,
+				   struct clk_core *core,
+				   unsigned long rate)
+{
+	while (core) {
+		struct clk_core *parent = core->parent;
+		unsigned long new_rate, min_rate, max_rate, best_parent_rate;
+		int ret;
+
+		clk_core_get_boundaries(core, &min_rate, &max_rate);
+			/* TODO: change to clk_core_can_round() */
+			if (clk_core_can_round(core)) {
+			struct clk_rate_request req;
+
+			req.rate = rate;
+			req.min_rate = min_rate;
+			req.max_rate = max_rate;
+
+			clk_core_init_rate_req(core, &req);
+
+			ret = clk_core_determine_round_nolock(core, &req);
+			if (ret < 0)
+				return -EINVAL;
+
+			best_parent_rate = req.best_parent_rate;
+			new_rate = req.rate;
+			parent = req.best_parent_hw ? req.best_parent_hw->core : NULL;
+
+			if (new_rate < min_rate || new_rate > max_rate)
+				return -EINVAL;
+		} else if (!parent || !(core->flags & CLK_SET_RATE_PARENT)) {
+			/* pass-through clock without adjustable parent */
+			return -EINVAL;
+		} else {
+			core = parent;
+			continue;
+		}
+
+		if (parent != core->parent || new_rate != core->rate) {
+			clk_add_change(pre_changes, &core->pre_change, parent,
+				       new_rate);
+			if (list_empty(&core->change.change_list))
+				clk_add_change(post_changes, &core->change,
+					       core->parent, core->rate);
+		}
+
+		core = parent;
+	}
+
+	return -EINVAL;
+}
+
+static int clk_pre_rate_req(struct list_head *pre_changes,
+			    struct list_head *post_changes,
+			    struct clk_core *core)
+{
+	struct clk_core *child, *parent = core->parent;
+	struct clk_rate_request next, pre;
+	struct clk_change *change, *tmp;
+	LIST_HEAD(tmp_list);
+	int ret;
+
+	/* The change list needs to be prepared already. */
+	WARN_ON(list_empty(&core->change.change_list));
+
+	if (!list_empty(&core->pre_change.change_list))
+		return -EINVAL;
+
+	list_add(&core->pre_change.change_list, &tmp_list);
+	while (!list_empty(&tmp_list)) {
+		change = list_first_entry(&tmp_list, struct clk_change,
+					  change_list);
+		core = change->core;
+		list_del_init(&core->pre_change.change_list);
+		hlist_for_each_entry(child, &core->children, child_node) {
+			if (!list_empty(&core->pre_change.change_list)) {
+				ret = -EINVAL;
+				goto err;
+			}
+
+			list_add(&child->pre_change.change_list, &tmp_list);
+		}
+
+		if (!core->ops->pre_rate_req)
+			continue;
+
+		parent = core->change.parent ? core->change.parent :
+			core->parent;
+		if (parent) {
+			next.best_parent_hw = parent->hw;
+			next.best_parent_rate = parent->change.rate;
+		}
+
+		next.rate = core->change.rate;
+		clk_core_get_boundaries(core, &next.min_rate, &next.max_rate);
+		ret = core->ops->pre_rate_req(core->hw, &next, &pre);
+		if (ret < 0)
+			goto err;
+
+		/*
+		 * A return value of 0 means that no pre_rate_req change is
+		 * needed.
+		 */
+		if (ret == 0)
+			continue;
+
+		parent = pre.best_parent_hw ? pre.best_parent_hw->core : NULL;
+		clk_add_change(pre_changes, &core->pre_change, parent,
+			       pre.rate);
+		if (parent != core->parent &&
+		    pre.best_parent_rate != parent->rate)
+			ret = clk_prepare_pre_changes(pre_changes, post_changes,
+						      parent,
+						      pre.best_parent_rate);
+	}
+
+	return 0;
+err:
+	list_for_each_entry_safe(change, tmp, &tmp_list, change_list)
+		list_del_init(&change->core->pre_change.change_list);
+
+	return ret;
+}
+
 /*
  * walk down a subtree and set the new rates notifying the rate
  * change on the way
@@ -2028,7 +2164,7 @@ static int clk_change_rate(struct clk_change *change)
 
 static int clk_change_rates(struct list_head *list)
 {
-	struct clk_change *change, *tmp;
+	struct clk_change *change;
 	int ret = 0;
 
 	/*
@@ -2099,13 +2235,25 @@ static unsigned long clk_core_req_round_rate_nolock(struct clk_core *core,
 	return ret ? 0 : req.rate;
 }
 
+static void clk_del_change_list_entries(struct list_head *changes)
+{
+	struct clk_change *change, *tmp;
+
+	list_for_each_entry_safe(change, tmp, changes, change_list) {
+		change->rate = 0;
+		change->parent = NULL;
+		list_del_init(&change->change_list);
+	}
+}
+
 static int clk_core_set_rate_nolock(struct clk_core *core,
 				    unsigned long req_rate)
 {
 	struct clk_core *top, *fail_clk, *child;
-	struct clk_change *change, *tmp;
 	unsigned long rate;
+	LIST_HEAD(pre_changes);
 	LIST_HEAD(changes);
+	LIST_HEAD(post_changes);
 	int ret = 0;
 
 	if (!core)
@@ -2133,7 +2281,7 @@ static int clk_core_set_rate_nolock(struct clk_core *core,
 	if (top != core) {
 		/* change.parent cannot be NULL in this case */
 		hlist_for_each_entry(child, &core->change.parent->children,
-				child_node)
+				     child_node)
 			clk_calc_subtree(child);
 	} else {
 		clk_calc_subtree(core);
@@ -2142,33 +2290,54 @@ static int clk_core_set_rate_nolock(struct clk_core *core,
 	/* Construct the list of changes */
 	clk_prepare_changes(&changes, top);
 
+	/* We need a separate list for these changes due to error handling. */
+	ret = clk_pre_rate_req(&pre_changes, &post_changes, top);
+	if (ret) {
+		pr_debug("%s: failed pre_rate_req via top clk %s: %d\n",
+			 __func__, top->name, ret);
+		goto pre_rate_req;
+	}
+
 	/* notify that we are about to change rates */
 	fail_clk = clk_propagate_rate_change(top, PRE_RATE_CHANGE);
 	if (fail_clk) {
 		pr_debug("%s: failed to set %s rate\n", __func__,
-				fail_clk->name);
-		clk_propagate_rate_change(top, ABORT_RATE_CHANGE);
+			 fail_clk->name);
 		ret = -EBUSY;
-		goto err;
+		goto prop_rate;
 	}
 
-	/* change the rates */
-	ret = clk_change_rates(&changes);
-	list_for_each_entry_safe(change, tmp, &changes, change_list) {
-		change->rate = 0;
-		change->parent = NULL;
-		list_del_init(&change->change_list);
+	ret = clk_change_rates(&pre_changes);
+	if (ret) {
+		pr_debug("%s: rate rate changes failed via top clk %s: %d\n",
+			 __func__, top->name, ret);
+		goto pre_rate_req;
 	}
 
+	/* change the rates */
+	ret = clk_change_rates(&changes);
+	clk_del_change_list_entries(&changes);
 	if (ret) {
 		pr_debug("%s: failed to set %s rate via top clk %s\n", __func__,
-				core->name, top->name);
-		clk_propagate_rate_change(top, ABORT_RATE_CHANGE);
-		goto err;
+			 core->name, top->name);
+		goto change_rates;
 	}
 
+	ret = clk_change_rates(&post_changes);
+	clk_del_change_list_entries(&post_changes);
+
+	clk_del_change_list_entries(&pre_changes);
 	core->req_rate = req_rate;
-err:
+
+	return 0;
+
+change_rates:
+	WARN_ON(clk_change_rates(&pre_changes));
+prop_rate:
+	clk_propagate_rate_change(top, ABORT_RATE_CHANGE);
+pre_rate_req:
+	clk_del_change_list_entries(&pre_changes);
+	clk_del_change_list_entries(&changes);
 	clk_pm_runtime_put(core);
 
 	return ret;
@@ -3186,7 +3355,9 @@ static int __clk_core_init(struct clk_core *core)
 
 	/* check that clk_ops are sane.  See Documentation/driver-api/clk.rst */
 	if (core->ops->set_rate &&
-	    !((core->ops->round_rate || core->ops->determine_rate) &&
+	    !((core->ops->round_rate ||
+	       core->ops->determine_rate ||
+	       core->ops->pre_rate_req) &&
 	      core->ops->recalc_rate)) {
 		pr_err("%s: %s must implement .round_rate or .determine_rate in addition to .recalc_rate\n",
 		       __func__, core->name);
@@ -3433,7 +3604,9 @@ struct clk *clk_register(struct device *dev, struct clk_hw *hw)
 	INIT_LIST_HEAD(&core->prepare_list);
 	INIT_LIST_HEAD(&core->enable_list);
 	INIT_LIST_HEAD(&core->change.change_list);
+	INIT_LIST_HEAD(&core->pre_change.change_list);
 	core->change.core = core;
+	core->pre_change.core = core;
 	hw->core = core;
 
 	/* allocate local copy in case parent_names is __initdata */
diff --git a/include/linux/clk-provider.h b/include/linux/clk-provider.h
index e443fa9fa859..c11ca22e2089 100644
--- a/include/linux/clk-provider.h
+++ b/include/linux/clk-provider.h
@@ -133,6 +133,13 @@ struct clk_duty {
  *		actually supported by the clock, and optionally the parent clock
  *		that should be used to provide the clock rate.
  *
+ * @pre_rate_req: Given the next state that the clk will enter via a
+ * 		clk_rate_request struct, next, fill in another clk_rate_request
+ * 		struct, pre, with any desired intermediate state to change to
+ * 		before the state in next is applied. Returns positive to request
+ * 		an intermediate state transition, 0 for no transition, and
+ * 		-EERROR otherwise.
+ *
  * @set_parent:	Change the input source of this clock; for clocks with multiple
  *		possible parents specify a new parent by passing in the index
  *		as a u8 corresponding to the parent in either the .parent_names
@@ -231,6 +238,9 @@ struct clk_ops {
 					unsigned long *parent_rate);
 	int		(*determine_rate)(struct clk_hw *hw,
 					  struct clk_rate_request *req);
+	int		(*pre_rate_req)(struct clk_hw *hw,
+					const struct clk_rate_request *next,
+					struct clk_rate_request *pre);
 	int		(*set_parent)(struct clk_hw *hw, u8 index);
 	u8		(*get_parent)(struct clk_hw *hw);
 	int		(*set_rate)(struct clk_hw *hw, unsigned long rate,
-- 
2.21.0.352.gf09ad66450-goog




[Index of Archives]     [Kernel Newbies]     [Security]     [Netfilter]     [Bugtraq]     [Linux FS]     [Yosemite Forum]     [MIPS Linux]     [ARM Linux]     [Linux Security]     [Linux RAID]     [Samba]     [Video 4 Linux]     [Device Mapper]     [Linux Resources]

  Powered by Linux