Commit 031dcc9b authored by Ulf Hansson's avatar Ulf Hansson Committed by Mike Turquette
Browse files

clk: Fixup errorhandling for clk_set_parent



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: default avatarUlf Hansson <ulf.hansson@linaro.org>
Cc: Rajagopal Venkat <rajagopal.venkat@linaro.org>
Signed-off-by: default avatarMike Turquette <mturquette@linaro.org>
parent b33d212f
Loading
Loading
Loading
Loading
+36 −20
Original line number Diff line number Diff line
@@ -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,12 +1355,15 @@ 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)
		__clk_prepare(parent);
@@ -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) {