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 | 136 +++++++++++++++++++++++++++++++++-- include/linux/clk-provider.h | 10 +++ 2 files changed, 139 insertions(+), 7 deletions(-) diff --git a/drivers/clk/clk.c b/drivers/clk/clk.c index 1db44b4e46b0..36a2f929ab8d 100644 --- a/drivers/clk/clk.c +++ b/drivers/clk/clk.c @@ -39,6 +39,7 @@ static int enable_refcnt; static HLIST_HEAD(clk_root_list); static HLIST_HEAD(clk_orphan_list); static LIST_HEAD(clk_notifier_list); +static LIST_HEAD(pre_change_free_list); /*** private data structures ***/ @@ -1896,6 +1897,74 @@ static struct clk_core *clk_propagate_rate_change(struct clk_core *core, return fail_clk; } +static int clk_pre_rate_req(struct list_head *pre_list, struct clk_core *core) +{ + struct clk_core *child, *parent = core->parent; + struct clk_rate_request next, pre; + struct clk_change *change; + int ret; + + hlist_for_each_entry(child, &core->children, child_node) { + ret = clk_pre_rate_req(pre_list, child); + if (ret) + return ret; + } + + if (core->new_child) { + ret = clk_pre_rate_req(pre_list, child); + if (ret) + return ret; + } + + if (!core->ops->pre_rate_req) + return 0; + + if (core->change.parent) + parent = core->change.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) + return ret; + else if (!ret) + goto out; + + /* + * We allocate a change for each clk with the pre_rate_req op. If we run + * out, that's because we wrapped around to a clk again in the + * pre_rate_req step which is not allowed. + */ + change = list_first_entry(&pre_change_free_list, struct clk_change, + change_list); + if (IS_ERR_OR_NULL(change)) { + pr_err("%s: pre_rate_req loop detected on clk %s. All pre_rate_req clk_change structs are used\n", + __func__, core->name); + return -EDEADLK; + } + + change->core = core; + change->rate = pre.rate; + change->parent = pre.best_parent_hw ? pre.best_parent_hw->core : NULL; + list_move(&change->change_list, pre_list); + +out: + /* If the pre req req pulls in a new parent, add it to the call chain */ + if (parent != change->parent) { + ret = clk_pre_rate_req(pre_list, change->parent); + if (ret) + return ret; + } + + return 0; +} + /* * walk down a subtree and set the new rates notifying the rate * change on the way @@ -2065,6 +2134,8 @@ static int clk_core_set_rate_nolock(struct clk_core *core, struct clk_core *top, *fail_clk; struct clk_change *change, *tmp; unsigned long rate; + LIST_HEAD(pre_changes); + LIST_HEAD(post_changes); LIST_HEAD(changes); int ret = 0; @@ -2092,6 +2163,14 @@ static int clk_core_set_rate_nolock(struct clk_core *core, clk_calc_subtree(core); + /* We need a separate list for these changes due to error handling. */ + ret = clk_pre_rate_req(&pre_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; + } + /* Construct the list of changes */ clk_prepare_changes(&changes, top); @@ -2100,11 +2179,19 @@ static int clk_core_set_rate_nolock(struct clk_core *core, if (fail_clk) { pr_debug("%s: failed to set %s rate\n", __func__, fail_clk->name); - clk_propagate_rate_change(top, ABORT_RATE_CHANGE); ret = -EBUSY; - goto err; + goto prop_rate; + } + + 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; } + list_splice_tail(&post_changes, &changes); + /* change the rates */ ret = clk_change_rates(&changes); list_for_each_entry_safe(change, tmp, &changes, change_list) { @@ -2112,16 +2199,28 @@ static int clk_core_set_rate_nolock(struct clk_core *core, change->parent = NULL; list_del_init(&change->change_list); } - 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; + goto change_rates; } + list_splice(&pre_changes, &pre_change_free_list); core->req_rate = req_rate; -err: + + return 0; + +change_rates: + WARN_ON(clk_change_rates(&pre_changes)); +pre_rate_req: + list_splice(&pre_changes, &pre_change_free_list); +prop_rate: + clk_propagate_rate_change(top, ABORT_RATE_CHANGE); + list_for_each_entry_safe(change, tmp, &changes, change_list) { + change->rate = 0; + change->parent = NULL; + list_del_init(&change->change_list); + } clk_pm_runtime_put(core); return ret; @@ -3139,7 +3238,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); @@ -3175,6 +3276,20 @@ static int __clk_core_init(struct clk_core *core) "%s: invalid NULL in %s's .parent_names\n", __func__, core->name); + /* Allocate a clk_change struct for pre_rate_reqs */ + if (core->ops->pre_rate_req) { + struct clk_change *change = kzalloc(sizeof(*change), + GFP_KERNEL); + if (!change) { + ret = -ENOMEM; + kfree(core->parents); + goto out; + } + + INIT_LIST_HEAD(&change->change_list); + list_add(&pre_change_free_list, &change->change_list); + } + core->parent = __clk_init_parent(core); /* @@ -3476,6 +3591,13 @@ static void __clk_release(struct kref *ref) while (--i >= 0) kfree_const(core->parent_names[i]); + if (core->ops->pre_rate_req) { + struct clk_change *change = + list_first_entry(&pre_change_free_list, + struct clk_change, change_list); + list_del(&change->change_list); + kfree(change); + } kfree(core->parent_names); kfree_const(core->name); kfree(core); diff --git a/include/linux/clk-provider.h b/include/linux/clk-provider.h index 60c51871b04b..98a65c6c326d 100644 --- a/include/linux/clk-provider.h +++ b/include/linux/clk-provider.h @@ -138,6 +138,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 @@ -236,6 +243,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.19.1.568.g152ad8e336-goog _______________________________________________ Linux-rockchip mailing list Linux-rockchip@xxxxxxxxxxxxxxxxxxx http://lists.infradead.org/mailman/listinfo/linux-rockchip