|
@@ -345,6 +345,21 @@ out:
|
|
|
return ret;
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * clk_debug_unregister - remove a clk node from the debugfs clk tree
|
|
|
+ * @clk: the clk being removed from the debugfs clk tree
|
|
|
+ *
|
|
|
+ * Dynamically removes a clk and all it's children clk nodes from the
|
|
|
+ * debugfs clk tree if clk->dentry points to debugfs created by
|
|
|
+ * clk_debug_register in __clk_init.
|
|
|
+ *
|
|
|
+ * Caller must hold prepare_lock.
|
|
|
+ */
|
|
|
+static void clk_debug_unregister(struct clk *clk)
|
|
|
+{
|
|
|
+ debugfs_remove_recursive(clk->dentry);
|
|
|
+}
|
|
|
+
|
|
|
/**
|
|
|
* clk_debug_reparent - reparent clk node in the debugfs clk tree
|
|
|
* @clk: the clk being reparented
|
|
@@ -435,6 +450,9 @@ static inline int clk_debug_register(struct clk *clk) { return 0; }
|
|
|
static inline void clk_debug_reparent(struct clk *clk, struct clk *new_parent)
|
|
|
{
|
|
|
}
|
|
|
+static inline void clk_debug_unregister(struct clk *clk)
|
|
|
+{
|
|
|
+}
|
|
|
#endif
|
|
|
|
|
|
/* caller must hold prepare_lock */
|
|
@@ -1778,6 +1796,7 @@ int __clk_init(struct device *dev, struct clk *clk)
|
|
|
|
|
|
clk_debug_register(clk);
|
|
|
|
|
|
+ kref_init(&clk->ref);
|
|
|
out:
|
|
|
clk_prepare_unlock();
|
|
|
|
|
@@ -1913,13 +1932,104 @@ fail_out:
|
|
|
}
|
|
|
EXPORT_SYMBOL_GPL(clk_register);
|
|
|
|
|
|
+/*
|
|
|
+ * Free memory allocated for a clock.
|
|
|
+ * Caller must hold prepare_lock.
|
|
|
+ */
|
|
|
+static void __clk_release(struct kref *ref)
|
|
|
+{
|
|
|
+ struct clk *clk = container_of(ref, struct clk, ref);
|
|
|
+ int i = clk->num_parents;
|
|
|
+
|
|
|
+ kfree(clk->parents);
|
|
|
+ while (--i >= 0)
|
|
|
+ kfree(clk->parent_names[i]);
|
|
|
+
|
|
|
+ kfree(clk->parent_names);
|
|
|
+ kfree(clk->name);
|
|
|
+ kfree(clk);
|
|
|
+}
|
|
|
+
|
|
|
+/*
|
|
|
+ * Empty clk_ops for unregistered clocks. These are used temporarily
|
|
|
+ * after clk_unregister() was called on a clock and until last clock
|
|
|
+ * consumer calls clk_put() and the struct clk object is freed.
|
|
|
+ */
|
|
|
+static int clk_nodrv_prepare_enable(struct clk_hw *hw)
|
|
|
+{
|
|
|
+ return -ENXIO;
|
|
|
+}
|
|
|
+
|
|
|
+static void clk_nodrv_disable_unprepare(struct clk_hw *hw)
|
|
|
+{
|
|
|
+ WARN_ON_ONCE(1);
|
|
|
+}
|
|
|
+
|
|
|
+static int clk_nodrv_set_rate(struct clk_hw *hw, unsigned long rate,
|
|
|
+ unsigned long parent_rate)
|
|
|
+{
|
|
|
+ return -ENXIO;
|
|
|
+}
|
|
|
+
|
|
|
+static int clk_nodrv_set_parent(struct clk_hw *hw, u8 index)
|
|
|
+{
|
|
|
+ return -ENXIO;
|
|
|
+}
|
|
|
+
|
|
|
+static const struct clk_ops clk_nodrv_ops = {
|
|
|
+ .enable = clk_nodrv_prepare_enable,
|
|
|
+ .disable = clk_nodrv_disable_unprepare,
|
|
|
+ .prepare = clk_nodrv_prepare_enable,
|
|
|
+ .unprepare = clk_nodrv_disable_unprepare,
|
|
|
+ .set_rate = clk_nodrv_set_rate,
|
|
|
+ .set_parent = clk_nodrv_set_parent,
|
|
|
+};
|
|
|
+
|
|
|
/**
|
|
|
* clk_unregister - unregister a currently registered clock
|
|
|
* @clk: clock to unregister
|
|
|
- *
|
|
|
- * Currently unimplemented.
|
|
|
*/
|
|
|
-void clk_unregister(struct clk *clk) {}
|
|
|
+void clk_unregister(struct clk *clk)
|
|
|
+{
|
|
|
+ unsigned long flags;
|
|
|
+
|
|
|
+ if (!clk || WARN_ON_ONCE(IS_ERR(clk)))
|
|
|
+ return;
|
|
|
+
|
|
|
+ clk_prepare_lock();
|
|
|
+
|
|
|
+ if (clk->ops == &clk_nodrv_ops) {
|
|
|
+ pr_err("%s: unregistered clock: %s\n", __func__, clk->name);
|
|
|
+ goto out;
|
|
|
+ }
|
|
|
+ /*
|
|
|
+ * Assign empty clock ops for consumers that might still hold
|
|
|
+ * a reference to this clock.
|
|
|
+ */
|
|
|
+ flags = clk_enable_lock();
|
|
|
+ clk->ops = &clk_nodrv_ops;
|
|
|
+ clk_enable_unlock(flags);
|
|
|
+
|
|
|
+ if (!hlist_empty(&clk->children)) {
|
|
|
+ struct clk *child;
|
|
|
+
|
|
|
+ /* Reparent all children to the orphan list. */
|
|
|
+ hlist_for_each_entry(child, &clk->children, child_node)
|
|
|
+ clk_set_parent(child, NULL);
|
|
|
+ }
|
|
|
+
|
|
|
+ clk_debug_unregister(clk);
|
|
|
+
|
|
|
+ hlist_del_init(&clk->child_node);
|
|
|
+
|
|
|
+ if (clk->prepare_count)
|
|
|
+ pr_warn("%s: unregistering prepared clock: %s\n",
|
|
|
+ __func__, clk->name);
|
|
|
+
|
|
|
+ kref_put(&clk->ref, __clk_release);
|
|
|
+out:
|
|
|
+ clk_prepare_unlock();
|
|
|
+}
|
|
|
EXPORT_SYMBOL_GPL(clk_unregister);
|
|
|
|
|
|
static void devm_clk_release(struct device *dev, void *res)
|
|
@@ -1987,6 +2097,7 @@ int __clk_get(struct clk *clk)
|
|
|
if (clk && !try_module_get(clk->owner))
|
|
|
return 0;
|
|
|
|
|
|
+ kref_get(&clk->ref);
|
|
|
return 1;
|
|
|
}
|
|
|
|
|
@@ -1995,6 +2106,10 @@ void __clk_put(struct clk *clk)
|
|
|
if (WARN_ON_ONCE(IS_ERR(clk)))
|
|
|
return;
|
|
|
|
|
|
+ clk_prepare_lock();
|
|
|
+ kref_put(&clk->ref, __clk_release);
|
|
|
+ clk_prepare_unlock();
|
|
|
+
|
|
|
if (clk)
|
|
|
module_put(clk->owner);
|
|
|
}
|