Introduce set_parent_hw and set_parent_done to support setting
parent in early kernel booting where we still can't schedule.

Change the input source of this clock hw; This callback
is intended to do the hw part setting of @set_parent work. It
should cooperate with @set_parent_done callback to do the whole
set parent work. The clock core will check @set_parent_done
in either sleep or polling way according to system state to
decide whether the whole set rate work is done.

Suggested-by: Thomas Gleixner <[email protected]>
Signed-off-by: Dong Aisheng <[email protected]>
---
 drivers/clk/clk.c            | 26 +++++++++++++++++++++++++-
 include/linux/clk-provider.h | 14 ++++++++++++++
 2 files changed, 39 insertions(+), 1 deletion(-)

diff --git a/drivers/clk/clk.c b/drivers/clk/clk.c
index 0d031b280c9a..9369dbb71118 100644
--- a/drivers/clk/clk.c
+++ b/drivers/clk/clk.c
@@ -1236,14 +1236,38 @@ static int __clk_set_parent(struct clk_core *core, 
struct clk_core *parent,
        unsigned long flags;
        int ret = 0;
        struct clk_core *old_parent;
+       unsigned long timeout;
 
        old_parent = __clk_set_parent_before(core, parent);
 
        trace_clk_set_parent(core, parent);
 
        /* change clock input source */
-       if (parent && core->ops->set_parent)
+       if (parent && core->ops->set_parent) {
                ret = core->ops->set_parent(core->hw, p_index);
+       } else if (parent && core->ops->set_parent_hw) {
+               ret = core->ops->set_parent_hw(core->hw, p_index);
+               if (!ret && core->ops->set_parent_done) {
+                       timeout = jiffies + msecs_to_jiffies(10);
+                       while (!core->ops->set_parent_done(core->hw)) {
+                               if (time_after(jiffies, timeout)) {
+                                       pr_err("%s: clock %s set parent 
timeout\n",
+                                               __func__, core->name);
+                                       ret = -ETIMEDOUT;
+                                       break;
+                               }
+                               if (system_state == SYSTEM_BOOTING)
+                                       /*
+                                        * Busy loop as we can't
+                                        * schedule in early boot
+                                        */
+                                       continue;
+                               else
+                                       usleep_range(core->delay_min,
+                                                    core->delay_max);
+                       }
+               }
+       }
 
        trace_clk_set_parent_complete(core, parent);
 
diff --git a/include/linux/clk-provider.h b/include/linux/clk-provider.h
index 3dcb99ad6bd2..16fa75cdd656 100644
--- a/include/linux/clk-provider.h
+++ b/include/linux/clk-provider.h
@@ -146,6 +146,18 @@ struct clk_rate_request {
  *             array index into the value programmed into the hardware.
  *             Returns 0 on success, -EERROR otherwise.
  *
+ * @set_parent_hw: Change the input source of this clock hw;  This callback
+ *             is intended to do the hw part setting of @set_parent work. It
+ *             should cooperate with @set_parent_done callback to do the whole
+ *             set parent work. The clock core will check @set_parent_done
+ *             in either sleep or polling way according to system state to
+ *             decide whether the whole set rate work is done. Optional
+ *             if @set_parent is used. This function must not sleep.
+ *
+ * @set_parent_done: Queries the hardware to determine if the set parent is
+ *             done. Optional, if this op is not set then the set parent
+ *             simply return. This function must not sleep.
+ *
  * @get_parent:        Queries the hardware to determine the parent of a 
clock.  The
  *             return value is a u8 which specifies the index corresponding to
  *             the parent clock.  This index can be applied to either the
@@ -243,6 +255,8 @@ struct clk_ops {
        int             (*determine_rate)(struct clk_hw *hw,
                                          struct clk_rate_request *req);
        int             (*set_parent)(struct clk_hw *hw, u8 index);
+       int             (*set_parent_hw)(struct clk_hw *hw, u8 index);
+       int             (*set_parent_done)(struct clk_hw *hw);
        u8              (*get_parent)(struct clk_hw *hw);
        int             (*set_rate)(struct clk_hw *hw, unsigned long rate,
                                    unsigned long parent_rate);
-- 
1.9.1

Reply via email to