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