diff --git a/drivers/clk/clk.c b/drivers/clk/clk.c
index e80ca1b..5ca8049 100644
--- a/drivers/clk/clk.c
+++ b/drivers/clk/clk.c
@@ -34,6 +34,29 @@ static struct dentry *rootdir;
 static struct dentry *orphandir;
 static int inited = 0;
 
+/* tree locking helpers */
+static void clk_lock_subtree(struct clk *clk)
+{
+	struct clk *child;
+	struct hlist_node *tmp;
+
+	mutex_lock_nested(&clk->lock, 0);
+
+	hlist_for_each_entry(child, tmp, &clk->children, child_node)
+		clk_lock_subtree(child);
+}
+
+static void clk_unlock_subtree(struct clk *clk)
+{
+	struct clk *child;
+	struct hlist_node *tmp;
+
+	hlist_for_each_entry(child, tmp, &clk->children, child_node)
+		clk_unlock_subtree(child);
+
+	mutex_unlock(&clk->lock);
+}
+
 /* caller must hold prepare_lock */
 static int clk_debug_create_one(struct clk *clk, struct dentry *pdentry)
 {
@@ -90,7 +113,7 @@ static int clk_debug_create_subtree(struct clk *clk, struct dentry *pdentry)
 {
 	struct clk *child;
 	struct hlist_node *tmp;
-	int ret = -EINVAL;;
+	int ret = -EINVAL;
 
 	if (!clk || !pdentry)
 		goto out;
@@ -126,7 +149,7 @@ static int clk_debug_register(struct clk *clk)
 	int ret = 0;
 
 	if (!inited)
-		goto out;
+		return ret;
 
 	parent = clk->parent;
 
@@ -145,9 +168,11 @@ static int clk_debug_register(struct clk *clk)
 		else
 			goto out;
 
+	clk_lock_subtree(clk);
 	ret = clk_debug_create_subtree(clk, pdentry);
 
 out:
+	clk_unlock_subtree(clk);
 	return ret;
 }
 
@@ -178,18 +203,20 @@ static int __init clk_debug_init(void)
 	if (!orphandir)
 		return -ENOMEM;
 
-	mutex_lock(&prepare_lock);
-
-	hlist_for_each_entry(clk, tmp, &clk_root_list, child_node)
+	hlist_for_each_entry(clk, tmp, &clk_root_list, child_node) {
+		/* FIXME clk_lock_subtree(clk);*/
 		clk_debug_create_subtree(clk, rootdir);
+		/* FIXME clk_unlock_subtree(clk); */
+	}
 
-	hlist_for_each_entry(clk, tmp, &clk_orphan_list, child_node)
+	hlist_for_each_entry(clk, tmp, &clk_orphan_list, child_node) {
+		/* FIXME clk_lock_subtree(clk);*/
 		clk_debug_create_subtree(clk, orphandir);
+		/* FIXME clk_unlock_subtree(clk);*/
+	}
 
 	inited = 1;
 
-	mutex_unlock(&prepare_lock);
-
 	return 0;
 }
 late_initcall(clk_debug_init);
@@ -233,16 +260,12 @@ static int clk_disable_unused(void)
 	struct clk *clk;
 	struct hlist_node *tmp;
 
-	mutex_lock(&prepare_lock);
-
 	hlist_for_each_entry(clk, tmp, &clk_root_list, child_node)
 		clk_disable_unused_subtree(clk);
 
 	hlist_for_each_entry(clk, tmp, &clk_orphan_list, child_node)
 		clk_disable_unused_subtree(clk);
 
-	mutex_unlock(&prepare_lock);
-
 	return 0;
 }
 late_initcall(clk_disable_unused);
@@ -370,6 +393,69 @@ struct clk *__clk_lookup(const char *name)
 	return NULL;
 }
 
+struct clk *__clk_lock_unprepare(struct clk *clk)
+{
+	struct clk *top = NULL;
+
+	if (!clk)
+		return NULL;
+
+	mutex_lock_nested(&clk->lock, 1);
+
+	if (clk->prepare_count > 1)
+		top = clk;
+	else {
+		top = __clk_lock_unprepare(clk->parent);
+		if (!top)
+			top = clk;
+	}
+
+	return top;
+}
+
+struct clk *__clk_lock_prepare(struct clk *clk)
+{
+	struct clk *top = NULL;
+
+	if (!clk)
+		return NULL;
+
+	mutex_lock_nested(&clk->lock, 1);
+
+	if (!clk->prepare_count) {
+		top = __clk_lock_prepare(clk->parent);
+		if (!top)
+			top = clk;
+	} else
+		top = clk;
+
+	return top;
+}
+
+void __clk_unlock_top(struct clk *clk, struct clk *top)
+{
+	if (clk != top)
+		__clk_unlock_top(clk->parent, top);
+
+	mutex_unlock(&clk->lock);
+}
+
+static inline int __clk_unlock_parent_chain(struct clk *clk, struct clk *top)
+{
+	struct clk *tmp;
+
+	tmp = clk;
+
+	while (tmp) {
+		mutex_unlock(&tmp->lock);
+		if (tmp == top)
+			break;
+		tmp = tmp->parent;
+	}
+
+	return 0;
+}
+
 /***        clk api        ***/
 
 void __clk_unprepare(struct clk *clk)
@@ -404,9 +490,16 @@ void __clk_unprepare(struct clk *clk)
  */
 void clk_unprepare(struct clk *clk)
 {
-	mutex_lock(&prepare_lock);
+	struct clk *top;
+
+	/* find the top-most clock we locked */
+	top = __clk_lock_unprepare(clk);
+
+	/* unprepare the clocks */
 	__clk_unprepare(clk);
-	mutex_unlock(&prepare_lock);
+
+	/* release the per-clock locks */
+	__clk_unlock_parent_chain(clk, top);
 }
 EXPORT_SYMBOL_GPL(clk_unprepare);
 
@@ -420,20 +513,21 @@ int __clk_prepare(struct clk *clk)
 	if (clk->prepare_count == 0) {
 		ret = __clk_prepare(clk->parent);
 		if (ret)
-			return ret;
+			goto out;
 
 		if (clk->ops->prepare) {
 			ret = clk->ops->prepare(clk->hw);
 			if (ret) {
 				__clk_unprepare(clk->parent);
-				return ret;
+				goto out;
 			}
 		}
 	}
 
 	clk->prepare_count++;
 
-	return 0;
+out:
+	return ret;
 }
 
 /**
@@ -450,11 +544,22 @@ int __clk_prepare(struct clk *clk)
  */
 int clk_prepare(struct clk *clk)
 {
+	struct clk *top;
 	int ret;
 
-	mutex_lock(&prepare_lock);
+	/* find the top-most clock we locked */
+	top = __clk_lock_prepare(clk);
+
+	if (!top) {
+		pr_err("%s: %s: could not be found!\n", __func__, clk->name);
+		return -EINVAL;
+	}
+
+	/* prepare the clocks */
 	ret = __clk_prepare(clk);
-	mutex_unlock(&prepare_lock);
+
+	/* release the per-clock locks */
+	__clk_unlock_parent_chain(clk, top);
 
 	return ret;
 }
@@ -565,9 +670,7 @@ unsigned long clk_get_rate(struct clk *clk)
 {
 	unsigned long rate;
 
-	mutex_lock(&prepare_lock);
 	rate = __clk_get_rate(clk);
-	mutex_unlock(&prepare_lock);
 
 	return rate;
 }
@@ -612,9 +715,7 @@ long clk_round_rate(struct clk *clk, unsigned long rate)
 {
 	unsigned long ret;
 
-	mutex_lock(&prepare_lock);
 	ret = __clk_round_rate(clk, rate);
-	mutex_unlock(&prepare_lock);
 
 	return ret;
 }
@@ -900,23 +1001,23 @@ int clk_set_rate(struct clk *clk, unsigned long rate)
 	struct clk *top, *fail_clk;
 	int ret = 0;
 
-	/* prevent racing with updates to the clock topology */
-	mutex_lock(&prepare_lock);
-
 	/* bail early if nothing to do */
 	if (rate == clk->rate)
-		goto out;
+		goto err_out;
 
 	if ((clk->flags & CLK_SET_RATE_GATE) && clk->prepare_count) {
 		ret = -EBUSY;
-		goto out;
+		goto err_out;
 	}
 
+	/* lock from the top-most possible clock */
+	clk_lock_subtree(clk->top);
+
 	/* calculate new rates and get the topmost changed clock */
 	top = clk_calc_new_rates(clk, rate);
 	if (!top) {
 		ret = -EINVAL;
-		goto out;
+		goto err_lock;
 	}
 
 	/* notify that we are about to change rates */
@@ -926,18 +1027,20 @@ int clk_set_rate(struct clk *clk, unsigned long rate)
 				fail_clk->name);
 		clk_propagate_rate_change(top, ABORT_RATE_CHANGE);
 		ret = -EBUSY;
-		goto out;
+		clk_unlock_subtree(clk->top);
+		return ret;
 	}
 
 	/* change the rates */
 	clk_change_rate(top);
 
-	mutex_unlock(&prepare_lock);
+	clk_unlock_subtree(clk->top);
 
 	return 0;
-out:
-	mutex_unlock(&prepare_lock);
 
+err_lock:
+	clk_unlock_subtree(clk->top);
+err_out:
 	return ret;
 }
 EXPORT_SYMBOL_GPL(clk_set_rate);
@@ -952,9 +1055,7 @@ struct clk *clk_get_parent(struct clk *clk)
 {
 	struct clk *parent;
 
-	mutex_lock(&prepare_lock);
 	parent = __clk_get_parent(clk);
-	mutex_unlock(&prepare_lock);
 
 	return parent;
 }
@@ -1064,10 +1165,11 @@ static int __clk_set_parent(struct clk *clk, struct clk *parent)
 	struct clk *old_parent;
 	unsigned long flags;
 	int ret = -EINVAL;
-	u8 i;
+	u8 i = 0;
 
 	old_parent = clk->parent;
 
+	pr_err("%s: OMG CLK_SET_PARENT OMG\n", __func__);
 	if (!clk->parents)
 		clk->parents = kzalloc((sizeof(struct clk*) * clk->num_parents),
 								GFP_KERNEL);
@@ -1135,15 +1237,13 @@ int clk_set_parent(struct clk *clk, struct clk *parent)
 {
 	int ret = 0;
 
+	pr_err("%s: %s\n", __func__, clk->name);
 	if (!clk || !clk->ops)
 		return -EINVAL;
 
 	if (!clk->ops->set_parent)
 		return -ENOSYS;
 
-	/* prevent racing with updates to the clock topology */
-	mutex_lock(&prepare_lock);
-
 	if (clk->parent == parent)
 		goto out;
 
@@ -1171,8 +1271,6 @@ int clk_set_parent(struct clk *clk, struct clk *parent)
 	__clk_reparent(clk, parent);
 
 out:
-	mutex_unlock(&prepare_lock);
-
 	return ret;
 }
 EXPORT_SYMBOL_GPL(clk_set_parent);
@@ -1194,8 +1292,6 @@ int __clk_init(struct device *dev, struct clk *clk)
 	if (!clk)
 		return -EINVAL;
 
-	mutex_lock(&prepare_lock);
-
 	/* check to see if a clock with this name is already registered */
 	if (__clk_lookup(clk->name)) {
 		pr_debug("%s: clk %s already initialized\n",
@@ -1296,6 +1392,16 @@ int __clk_init(struct device *dev, struct clk *clk)
 				break;
 			}
 
+	/* XXX initialize per-clock mutex */
+	__mutex_init(&clk->lock, clk->name, &clk->lock_key);
+
+	/*
+	 * calculate the top-most clock that might change when setting this
+	 * clock's rate
+	 */
+	/* FIXME assume CLK_SET_RATE_PARENT is never set */
+	clk->top = clk;
+
 	/*
 	 * optional platform-specific magic
 	 *
@@ -1310,8 +1416,6 @@ int __clk_init(struct device *dev, struct clk *clk)
 	clk_debug_register(clk);
 
 out:
-	mutex_unlock(&prepare_lock);
-
 	return ret;
 }
 
@@ -1476,8 +1580,6 @@ int clk_notifier_register(struct clk *clk, struct notifier_block *nb)
 	if (!clk || !nb)
 		return -EINVAL;
 
-	mutex_lock(&prepare_lock);
-
 	/* search the list of notifiers for this clk */
 	list_for_each_entry(cn, &clk_notifier_list, node)
 		if (cn->clk == clk)
@@ -1500,8 +1602,6 @@ int clk_notifier_register(struct clk *clk, struct notifier_block *nb)
 	clk->notifier_count++;
 
 out:
-	mutex_unlock(&prepare_lock);
-
 	return ret;
 }
 EXPORT_SYMBOL_GPL(clk_notifier_register);
@@ -1525,8 +1625,6 @@ int clk_notifier_unregister(struct clk *clk, struct notifier_block *nb)
 	if (!clk || !nb)
 		return -EINVAL;
 
-	mutex_lock(&prepare_lock);
-
 	list_for_each_entry(cn, &clk_notifier_list, node)
 		if (cn->clk == clk)
 			break;
@@ -1546,8 +1644,6 @@ int clk_notifier_unregister(struct clk *clk, struct notifier_block *nb)
 		ret = -ENOENT;
 	}
 
-	mutex_unlock(&prepare_lock);
-
 	return ret;
 }
 EXPORT_SYMBOL_GPL(clk_notifier_unregister);
diff --git a/include/linux/clk-private.h b/include/linux/clk-private.h
index 9c7f580..edcf07a 100644
--- a/include/linux/clk-private.h
+++ b/include/linux/clk-private.h
@@ -41,6 +41,11 @@ struct clk {
 	struct hlist_head	children;
 	struct hlist_node	child_node;
 	unsigned int		notifier_count;
+	struct mutex		lock;
+	struct lock_class_key	lock_key;
+	struct clk		*top;
+	int			depth;
+	struct clk		**parent_chain;
 #ifdef CONFIG_COMMON_CLK_DEBUG
 	struct dentry		*dentry;
 #endif
