lkml.org 
[lkml]   [2019]   [Mar]   [4]   [last100]   RSS Feed
Views: [wrap][no wrap]   [headers]  [forward] 
 
Messages in this thread
Patch in this message
/
From
Subject[PATCH v2 4/6] clk: add coordinated clk changes support
Date
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@chromium.org>
---
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
\
 
 \ /
  Last update: 2019-03-05 05:50    [W:0.062 / U:1.636 seconds]
©2003-2020 Jasper Spaans|hosted at Digital Ocean and TransIP|Read the blog|Advertise on this site