Subject: [RFC/PATCH 02/12] clk: Add safe switch hook

From: [email-masked] (Mike Turquette)
Date: Mon, 28 Jul 2014 23:05:29 -0700

Quoting Stephen Boyd (2014-06-24 17:06:13)
> Sometimes clocks can't accept their parent source turning off
> while the source is reprogrammed to a different rate. Most
> notably some CPU clocks require a way to switch away from the
> current PLL they're running on, reprogram that PLL to a new rate,
> and then switch back to the PLL with the new rate once they're
> done.  Add a hook that drivers can implement allowing them to
> return a 'safe parent' that they can switch their parent to while
> the upstream source is reprogrammed.

Adding Thomas to Cc.

Thomas,

Does this generic hook help you out at all with your CPU frequency
transitions? I remember in my discussions with Chander K. that you have
something like safe dividers, safe parents and safe voltages to take
into account (but I might be misremembering some of that).

Stephen,

For reference, recent patches from Samsung to introduce cpu clocks[1]
which I am not wild about, but the generic infrastructure isn't really
there yet in the framework core to manage complex, pre-defined,
multi-clock transitions gracefully.

Regards,
Mike

[1] http://www.spinics.net/lists/arm-kernel/msg351137.html

> 
> Signed-off-by: Stephen Boyd <[email-masked]>
> ---
>  drivers/clk/clk.c            | 53 ++++++++++++++++++++++++++++++++++++++------
>  include/linux/clk-private.h  |  2 ++
>  include/linux/clk-provider.h |  1 +
>  3 files changed, 49 insertions(+), 7 deletions(-)
> 
> diff --git a/drivers/clk/clk.c b/drivers/clk/clk.c
> index 8b73edef151d..5e32fa55032b 100644
> --- a/drivers/clk/clk.c
> +++ b/drivers/clk/clk.c
> @@ -1367,6 +1367,7 @@ static void clk_calc_subtree(struct clk *clk, unsigned long new_rate,
>                              struct clk *new_parent, u8 p_index)
>  {
>         struct clk *child;
> +       struct clk *parent;
>  
>         clk->new_rate = new_rate;
>         clk->new_parent = new_parent;
> @@ -1376,6 +1377,17 @@ static void clk_calc_subtree(struct clk *clk, unsigned long new_rate,
>         if (new_parent && new_parent != clk->parent)
>                 new_parent->new_child = clk;
>  
> +       if (clk->ops->get_safe_parent) {
> +               parent = clk->ops->get_safe_parent(clk->hw);
> +               if (parent) {
> +                       p_index = clk_fetch_parent_index(clk, parent);
> +                       clk->safe_parent_index = p_index;
> +                       clk->safe_parent = parent;
> +               }
> +       } else {
> +               clk->safe_parent = NULL;
> +       }
> +
>         hlist_for_each_entry(child, &clk->children, child_node) {
>                 child->new_rate = clk_recalc(child, new_rate);
>                 clk_calc_subtree(child, child->new_rate, NULL, 0);
> @@ -1458,14 +1470,42 @@ out:
>  static struct clk *clk_propagate_rate_change(struct clk *clk, unsigned long event)
>  {
>         struct clk *child, *tmp_clk, *fail_clk = NULL;
> +       struct clk *old_parent;
>         int ret = NOTIFY_DONE;
>  
> -       if (clk->rate == clk->new_rate)
> +       if (clk->rate == clk->new_rate && event != POST_RATE_CHANGE)
>                 return NULL;
>  
> +       switch (event) {
> +       case PRE_RATE_CHANGE:
> +               if (clk->safe_parent)
> +                       clk->ops->set_parent(clk->hw, clk->safe_parent_index);
> +               break;
> +       case POST_RATE_CHANGE:
> +               if (clk->safe_parent) {
> +                       old_parent = __clk_set_parent_before(clk,
> +                                                            clk->new_parent);
> +                       if (clk->ops->set_rate_and_parent) {
> +                               clk->ops->set_rate_and_parent(clk->hw,
> +                                               clk->new_rate,
> +                                               clk->new_parent ?
> +                                               clk->new_parent->rate : 0,
> +                                               clk->new_parent_index);
> +                       } else if (clk->ops->set_parent) {
> +                               clk->ops->set_parent(clk->hw,
> +                                               clk->new_parent_index);
> +                       }
> +                       __clk_set_parent_after(clk, clk->new_parent,
> +                                              old_parent);
> +               }
> +               break;
> +       }
> +
>         if (clk->notifier_count) {
> -               ret = __clk_notify(clk, event, clk->rate, clk->new_rate);
> -               if (ret & NOTIFY_STOP_MASK)
> +               if (event != POST_RATE_CHANGE)
> +                       ret = __clk_notify(clk, event, clk->rate,
> +                                          clk->new_rate);
> +               if (ret & NOTIFY_STOP_MASK && event != POST_RATE_CHANGE)
>                         fail_clk = clk;
>         }
>  
> @@ -1507,7 +1547,8 @@ static void clk_change_rate(struct clk *clk)
>         else if (clk->parent)
>                 best_parent_rate = clk->parent->rate;
>  
> -       if (clk->new_parent && clk->new_parent != clk->parent) {
> +       if (clk->new_parent && clk->new_parent != clk->parent &&
> +                       !clk->safe_parent) {
>                 old_parent = __clk_set_parent_before(clk, clk->new_parent);
>  
>                 if (clk->ops->set_rate_and_parent) {
> @@ -1527,9 +1568,6 @@ static void clk_change_rate(struct clk *clk)
>  
>         clk->rate = clk_recalc(clk, best_parent_rate);
>  
> -       if (clk->notifier_count && old_rate != clk->rate)
> -               __clk_notify(clk, POST_RATE_CHANGE, old_rate, clk->rate);
> -
>         hlist_for_each_entry(child, &clk->children, child_node) {
>                 /* Skip children who will be reparented to another clock */
>                 if (child->new_parent && child->new_parent != clk)
> @@ -1603,6 +1641,7 @@ int clk_set_rate(struct clk *clk, unsigned long rate)
>         /* change the rates */
>         clk_change_rate(top);
>  
> +       clk_propagate_rate_change(top, POST_RATE_CHANGE);
>  out:
>         clk_prepare_unlock();
>  
> diff --git a/include/linux/clk-private.h b/include/linux/clk-private.h
> index efbf70b9fd84..f48684af4d8f 100644
> --- a/include/linux/clk-private.h
> +++ b/include/linux/clk-private.h
> @@ -38,8 +38,10 @@ struct clk {
>         struct clk              **parents;
>         u8                      num_parents;
>         u8                      new_parent_index;
> +       u8                      safe_parent_index;
>         unsigned long           rate;
>         unsigned long           new_rate;
> +       struct clk              *safe_parent;
>         struct clk              *new_parent;
>         struct clk              *new_child;
>         unsigned long           flags;
> diff --git a/include/linux/clk-provider.h b/include/linux/clk-provider.h
> index 0c287dbbb144..7485911df4b6 100644
> --- a/include/linux/clk-provider.h
> +++ b/include/linux/clk-provider.h
> @@ -170,6 +170,7 @@ struct clk_ops {
>                                         struct clk **best_parent_clk);
>         int             (*set_parent)(struct clk_hw *hw, u8 index);
>         u8              (*get_parent)(struct clk_hw *hw);
> +       struct clk      *(*get_safe_parent)(struct clk_hw *hw);
>         int             (*set_rate)(struct clk_hw *hw, unsigned long rate,
>                                     unsigned long parent_rate);
>         int             (*set_rate_and_parent)(struct clk_hw *hw,
> -- 
> The Qualcomm Innovation Center, Inc. is a member of the Code Aurora Forum,
> hosted by The Linux Foundation
>