#include <linux/init.h>
#include <linux/sched.h>
+#include "clk.h"
+
static DEFINE_SPINLOCK(enable_lock);
static DEFINE_MUTEX(prepare_lock);
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
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 */
{
return !clk ? NULL : clk->hw;
}
+EXPORT_SYMBOL_GPL(__clk_get_hw);
u8 __clk_get_num_parents(struct clk *clk)
{
return !clk ? 0 : clk->num_parents;
}
+EXPORT_SYMBOL_GPL(__clk_get_num_parents);
struct clk *__clk_get_parent(struct clk *clk)
{
return !clk ? NULL : clk->parent;
}
+EXPORT_SYMBOL_GPL(__clk_get_parent);
struct clk *clk_get_parent_by_index(struct clk *clk, u8 index)
{
else
return clk->parents[index];
}
+EXPORT_SYMBOL_GPL(clk_get_parent_by_index);
unsigned int __clk_get_enable_count(struct clk *clk)
{
out:
return ret;
}
+EXPORT_SYMBOL_GPL(__clk_get_rate);
unsigned long __clk_get_accuracy(struct clk *clk)
{
out:
return !!ret;
}
+EXPORT_SYMBOL_GPL(__clk_is_enabled);
static struct clk *__clk_lookup_subtree(const char *name, struct clk *clk)
{
return best;
}
+EXPORT_SYMBOL_GPL(__clk_mux_determine_rate);
/*** clk api ***/
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;
/*
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);
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;
}
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)
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],
if (clk->ops->init)
clk->ops->init(clk->hw);
+ kref_init(&clk->ref);
out:
clk_prepare_unlock();
clk->flags = hw->init->flags;
clk->parent_names = hw->init->parent_names;
clk->num_parents = hw->init->num_parents;
+ if (dev && dev->driver)
+ clk->owner = dev->driver->owner;
+ else
+ clk->owner = NULL;
ret = __clk_init(dev, clk);
if (ret)
goto fail_name;
}
clk->ops = hw->init->ops;
+ if (dev && dev->driver)
+ clk->owner = dev->driver->owner;
clk->hw = hw;
clk->flags = hw->init->flags;
clk->num_parents = hw->init->num_parents;
}
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)
}
EXPORT_SYMBOL_GPL(devm_clk_unregister);
+/*
+ * clkdev helpers
+ */
+int __clk_get(struct clk *clk)
+{
+ if (clk && !try_module_get(clk->owner))
+ return 0;
+
+ kref_get(&clk->ref);
+ return 1;
+}
+
+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);
+}
+
/*** clk rate change notifiers ***/
/**
void *data;
};
-extern struct of_device_id __clk_of_table[];
-
static const struct of_device_id __clk_of_table_sentinel
__used __section(__clk_of_table_end);
static LIST_HEAD(of_clk_providers);
-static DEFINE_MUTEX(of_clk_lock);
+static DEFINE_MUTEX(of_clk_mutex);
+
+/* of_clk_provider list locking helpers */
+void of_clk_lock(void)
+{
+ mutex_lock(&of_clk_mutex);
+}
+
+void of_clk_unlock(void)
+{
+ mutex_unlock(&of_clk_mutex);
+}
struct clk *of_clk_src_simple_get(struct of_phandle_args *clkspec,
void *data)
cp->data = data;
cp->get = clk_src_get;
- mutex_lock(&of_clk_lock);
+ mutex_lock(&of_clk_mutex);
list_add(&cp->link, &of_clk_providers);
- mutex_unlock(&of_clk_lock);
+ mutex_unlock(&of_clk_mutex);
pr_debug("Added clock from %s\n", np->full_name);
return 0;
{
struct of_clk_provider *cp;
- mutex_lock(&of_clk_lock);
+ mutex_lock(&of_clk_mutex);
list_for_each_entry(cp, &of_clk_providers, link) {
if (cp->node == np) {
list_del(&cp->link);
break;
}
}
- mutex_unlock(&of_clk_lock);
+ mutex_unlock(&of_clk_mutex);
}
EXPORT_SYMBOL_GPL(of_clk_del_provider);
-struct clk *of_clk_get_from_provider(struct of_phandle_args *clkspec)
+struct clk *__of_clk_get_from_provider(struct of_phandle_args *clkspec)
{
struct of_clk_provider *provider;
struct clk *clk = ERR_PTR(-ENOENT);
/* Check if we have such a provider in our array */
- mutex_lock(&of_clk_lock);
list_for_each_entry(provider, &of_clk_providers, link) {
if (provider->node == clkspec->np)
clk = provider->get(clkspec, provider->data);
if (!IS_ERR(clk))
break;
}
- mutex_unlock(&of_clk_lock);
+
+ return clk;
+}
+
+struct clk *of_clk_get_from_provider(struct of_phandle_args *clkspec)
+{
+ struct clk *clk;
+
+ mutex_lock(&of_clk_mutex);
+ clk = __of_clk_get_from_provider(clkspec);
+ mutex_unlock(&of_clk_mutex);
return clk;
}
struct device_node *np;
if (!matches)
- matches = __clk_of_table;
+ matches = &__clk_of_table;
for_each_matching_node_and_match(np, matches, &match) {
of_clk_init_cb_t clk_init_cb = match->data;