clk: Fixup errorhandling for clk_set_parent
authorUlf Hansson <ulf.hansson@linaro.org>
Tue, 2 Apr 2013 21:09:38 +0000 (23:09 +0200)
committerMike Turquette <mturquette@linaro.org>
Tue, 9 Apr 2013 01:19:32 +0000 (18:19 -0700)
Fixup the broken feature of allowing reparent of a clk to the
orhpan list and vice verse. When operating on a single-parent
clk, the .set_parent callback for the clk hw is optional to
implement, but for a multi-parent clk it is mandatory.

Moreover improve the errorhandling by verifying the prerequisites
before triggering clk notifiers. This will prevent unnecessary
rollback with ABORT_RATE_CHANGE.

Signed-off-by: Ulf Hansson <ulf.hansson@linaro.org>
Cc: Rajagopal Venkat <rajagopal.venkat@linaro.org>
Signed-off-by: Mike Turquette <mturquette@linaro.org>
drivers/clk/clk.c

index 013a3c7fea5b6bc0fbb101edecad5c7fcd593b23..c83e8e543bab589a6ee0824108cddbf73b4b6556 100644 (file)
@@ -1332,15 +1332,10 @@ void __clk_reparent(struct clk *clk, struct clk *new_parent)
        __clk_recalc_rates(clk, POST_RATE_CHANGE);
 }
 
-static int __clk_set_parent(struct clk *clk, struct clk *parent)
+static u8 clk_fetch_parent_index(struct clk *clk, struct clk *parent)
 {
-       struct clk *old_parent;
-       unsigned long flags;
-       int ret = -EINVAL;
        u8 i;
 
-       old_parent = clk->parent;
-
        if (!clk->parents)
                clk->parents = kzalloc((sizeof(struct clk*) * clk->num_parents),
                                                                GFP_KERNEL);
@@ -1360,11 +1355,14 @@ static int __clk_set_parent(struct clk *clk, struct clk *parent)
                }
        }
 
-       if (i == clk->num_parents) {
-               pr_debug("%s: clock %s is not a possible parent of clock %s\n",
-                               __func__, parent->name, clk->name);
-               goto out;
-       }
+       return i;
+}
+
+static int __clk_set_parent(struct clk *clk, struct clk *parent, u8 p_index)
+{
+       unsigned long flags;
+       int ret = 0;
+       struct clk *old_parent = clk->parent;
 
        /* migrate prepare and enable */
        if (clk->prepare_count)
@@ -1377,7 +1375,8 @@ static int __clk_set_parent(struct clk *clk, struct clk *parent)
        clk_enable_unlock(flags);
 
        /* change clock input source */
-       ret = clk->ops->set_parent(clk->hw, i);
+       if (parent && clk->ops->set_parent)
+               ret = clk->ops->set_parent(clk->hw, p_index);
 
        /* clean up old prepare and enable */
        flags = clk_enable_lock();
@@ -1388,7 +1387,6 @@ static int __clk_set_parent(struct clk *clk, struct clk *parent)
        if (clk->prepare_count)
                __clk_unprepare(old_parent);
 
-out:
        return ret;
 }
 
@@ -1407,11 +1405,14 @@ out:
 int clk_set_parent(struct clk *clk, struct clk *parent)
 {
        int ret = 0;
+       u8 p_index = 0;
+       unsigned long p_rate = 0;
 
        if (!clk || !clk->ops)
                return -EINVAL;
 
-       if (!clk->ops->set_parent)
+       /* verify ops for for multi-parent clks */
+       if ((clk->num_parents > 1) && (!clk->ops->set_parent))
                return -ENOSYS;
 
        /* prevent racing with updates to the clock topology */
@@ -1420,19 +1421,34 @@ int clk_set_parent(struct clk *clk, struct clk *parent)
        if (clk->parent == parent)
                goto out;
 
+       /* check that we are allowed to re-parent if the clock is in use */
+       if ((clk->flags & CLK_SET_PARENT_GATE) && clk->prepare_count) {
+               ret = -EBUSY;
+               goto out;
+       }
+
+       /* try finding the new parent index */
+       if (parent) {
+               p_index = clk_fetch_parent_index(clk, parent);
+               p_rate = parent->rate;
+               if (p_index == clk->num_parents) {
+                       pr_debug("%s: clk %s can not be parent of clk %s\n",
+                                       __func__, parent->name, clk->name);
+                       ret = -EINVAL;
+                       goto out;
+               }
+       }
+
        /* propagate PRE_RATE_CHANGE notifications */
        if (clk->notifier_count)
-               ret = __clk_speculate_rates(clk, parent->rate);
+               ret = __clk_speculate_rates(clk, p_rate);
 
        /* abort if a driver objects */
        if (ret == NOTIFY_STOP)
                goto out;
 
-       /* only re-parent if the clock is not in use */
-       if ((clk->flags & CLK_SET_PARENT_GATE) && clk->prepare_count)
-               ret = -EBUSY;
-       else
-               ret = __clk_set_parent(clk, parent);
+       /* do the re-parent */
+       ret = __clk_set_parent(clk, parent, p_index);
 
        /* propagate ABORT_RATE_CHANGE if .set_parent failed */
        if (ret) {