]> Pileus Git - ~andy/linux/blobdiff - drivers/clk/clk.c
clk: Add set_rate_and_parent() op
[~andy/linux] / drivers / clk / clk.c
index e3e03270b95e06498b1f650860f6dcc9b1da6f9a..2b38dc99063f1b6574df031ccea84af5df9cb9e6 100644 (file)
@@ -1218,10 +1218,9 @@ static void clk_reparent(struct clk *clk, struct clk *new_parent)
        clk->parent = new_parent;
 }
 
-static int __clk_set_parent(struct clk *clk, struct clk *parent, u8 p_index)
+static struct clk *__clk_set_parent_before(struct clk *clk, struct clk *parent)
 {
        unsigned long flags;
-       int ret = 0;
        struct clk *old_parent = clk->parent;
 
        /*
@@ -1252,6 +1251,34 @@ static int __clk_set_parent(struct clk *clk, struct clk *parent, u8 p_index)
        clk_reparent(clk, parent);
        clk_enable_unlock(flags);
 
+       return old_parent;
+}
+
+static void __clk_set_parent_after(struct clk *clk, struct clk *parent,
+               struct clk *old_parent)
+{
+       /*
+        * Finish the migration of prepare state and undo the changes done
+        * for preventing a race with clk_enable().
+        */
+       if (clk->prepare_count) {
+               clk_disable(clk);
+               clk_disable(old_parent);
+               __clk_unprepare(old_parent);
+       }
+
+       /* update debugfs with new clk tree topology */
+       clk_debug_reparent(clk, parent);
+}
+
+static int __clk_set_parent(struct clk *clk, struct clk *parent, u8 p_index)
+{
+       unsigned long flags;
+       int ret = 0;
+       struct clk *old_parent;
+
+       old_parent = __clk_set_parent_before(clk, parent);
+
        /* change clock input source */
        if (parent && clk->ops->set_parent)
                ret = clk->ops->set_parent(clk->hw, p_index);
@@ -1269,18 +1296,8 @@ static int __clk_set_parent(struct clk *clk, struct clk *parent, u8 p_index)
                return ret;
        }
 
-       /*
-        * Finish the migration of prepare state and undo the changes done
-        * for preventing a race with clk_enable().
-        */
-       if (clk->prepare_count) {
-               clk_disable(clk);
-               clk_disable(old_parent);
-               __clk_unprepare(old_parent);
-       }
+       __clk_set_parent_after(clk, parent, old_parent);
 
-       /* update debugfs with new clk tree topology */
-       clk_debug_reparent(clk, parent);
        return 0;
 }
 
@@ -1465,17 +1482,32 @@ static void clk_change_rate(struct clk *clk)
        struct clk *child;
        unsigned long old_rate;
        unsigned long best_parent_rate = 0;
+       bool skip_set_rate = false;
+       struct clk *old_parent;
 
        old_rate = clk->rate;
 
-       /* set parent */
-       if (clk->new_parent && clk->new_parent != clk->parent)
-               __clk_set_parent(clk, clk->new_parent, clk->new_parent_index);
-
-       if (clk->parent)
+       if (clk->new_parent)
+               best_parent_rate = clk->new_parent->rate;
+       else if (clk->parent)
                best_parent_rate = clk->parent->rate;
 
-       if (clk->ops->set_rate)
+       if (clk->new_parent && clk->new_parent != clk->parent) {
+               old_parent = __clk_set_parent_before(clk, clk->new_parent);
+
+               if (clk->ops->set_rate_and_parent) {
+                       skip_set_rate = true;
+                       clk->ops->set_rate_and_parent(clk->hw, clk->new_rate,
+                                       best_parent_rate,
+                                       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);
+       }
+
+       if (!skip_set_rate && clk->ops->set_rate)
                clk->ops->set_rate(clk->hw, clk->new_rate, best_parent_rate);
 
        if (clk->ops->recalc_rate)
@@ -1770,6 +1802,14 @@ int __clk_init(struct device *dev, struct clk *clk)
                goto out;
        }
 
+       if (clk->ops->set_rate_and_parent &&
+                       !(clk->ops->set_parent && clk->ops->set_rate)) {
+               pr_warn("%s: %s must implement .set_parent & .set_rate\n",
+                               __func__, clk->name);
+               ret = -EINVAL;
+               goto out;
+       }
+
        /* throw a WARN if any entries in parent_names are NULL */
        for (i = 0; i < clk->num_parents; i++)
                WARN(!clk->parent_names[i],