[RFC][PATCH 1/2] Clocklib: add generic framework for managingclocks.
From: Dmitry Baryshkov
Date: Thu May 22 2008 - 17:23:17 EST
Provide a generic framework that platform may choose
to support clocks api. In particular this provides
platform-independant struct clk definition, a full
implementation of clocks api and a set of functions
for registering and unregistering clocks in a safe way.
Signed-off-by: Dmitry Baryshkov <dbaryshkov@xxxxxxxxx>
---
include/linux/clocklib.h | 64 +++++++
lib/Kconfig | 3 +
lib/Makefile | 1 +
lib/clocklib.c | 432 ++++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 500 insertions(+), 0 deletions(-)
create mode 100644 include/linux/clocklib.h
create mode 100644 lib/clocklib.c
diff --git a/include/linux/clocklib.h b/include/linux/clocklib.h
new file mode 100644
index 0000000..31ca048
--- /dev/null
+++ b/include/linux/clocklib.h
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2008 Dmitry Baryshkov
+ *
+ * This file is released under the GPL v2.
+ */
+#ifndef CLOCKLIB_H
+#define CLOCKLIB_H
+
+struct device;
+struct clk;
+
+/**
+ * struct clk_ops - generic clock management operations
+ * @can_get: checks whether passed device can get this clock
+ * @set_parent: reconfigures the clock to use specified parent
+ * @set_mode: enable or disable specified clock.
+ * @get_rate: obtain the current clock rate of a specified clock
+ * @round_rate: adjust a reate to the exact rate a clock can provide and possibly apply it
+ * @format: output any additional information for a clock
+ *
+ * This structure specifies clock operations that are used to configure
+ * specific clock.
+ */
+struct clk_ops {
+ int (*can_get)(struct clk *clk, void *data, struct device *dev);
+ int (*set_parent)(struct clk *clk, void *data, struct clk *parent);
+ int (*set_mode)(struct clk *clk, void *data, bool enable);
+ unsigned long (*get_rate)(struct clk *clk, void *data);
+ long int (*round_rate)(struct clk *clk, void *data, unsigned long rate, bool apply);
+};
+
+
+struct clk *clk_alloc(struct clk *parent, const struct clk_ops *ops,
+ void * data, const char *fmt, ...)
+ __attribute__((format(printf, 4, 5)));
+void clk_free(struct clk *clk);
+
+/*
+ * struct clk_table - a helper struct to list clocks and their parents in the table
+ * @parent_name - the name of parent clock if you'd like to find it at the registration time
+ * @parent_dev - the device corresponding to the parent clock
+ * @name - the name of the clock
+ * @ops - operations corresponding to the clock
+ * @data - clock-specific data
+ * @clk - this receives the new clock
+ */
+struct clk_table {
+ const char *parent_name;
+ struct device *parent_dev;
+
+ const char *name;
+ const struct clk_ops *ops;
+ void *data;
+
+ struct clk *clk;
+};
+
+int clks_register(struct clk_table *clks, int size);
+
+extern const struct clk_ops clk_dev_ops;
+
+#endif
+
+
diff --git a/lib/Kconfig b/lib/Kconfig
index 8cc8e87..f50b04a 100644
--- a/lib/Kconfig
+++ b/lib/Kconfig
@@ -13,6 +13,9 @@ config GENERIC_FIND_FIRST_BIT
config GENERIC_FIND_NEXT_BIT
def_bool n
+config CLOCKLIB
+ tristate
+
config CRC_CCITT
tristate "CRC-CCITT functions"
help
diff --git a/lib/Makefile b/lib/Makefile
index 74b0cfb..c5b4cc3 100644
--- a/lib/Makefile
+++ b/lib/Makefile
@@ -37,6 +37,7 @@ obj-$(CONFIG_PLIST) += plist.o
obj-$(CONFIG_DEBUG_PREEMPT) += smp_processor_id.o
obj-$(CONFIG_DEBUG_LIST) += list_debug.o
obj-$(CONFIG_DEBUG_OBJECTS) += debugobjects.o
+lib-$(CONFIG_CLOCKLIB) += clocklib.o
ifneq ($(CONFIG_HAVE_DEC_LOCK),y)
lib-y += dec_and_lock.o
diff --git a/lib/clocklib.c b/lib/clocklib.c
new file mode 100644
index 0000000..9fda0cd
--- /dev/null
+++ b/lib/clocklib.c
@@ -0,0 +1,432 @@
+/*
+ * lib/clocklib.c
+ *
+ * Copyright (C) 2008 Dmitry Baryshkov
+ *
+ * Generic clocks API implementation
+ *
+ * This file is released under the GPL v2.
+ */
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/clocklib.h>
+#include <linux/kobject.h>
+#include <linux/err.h>
+#include <linux/spinlock.h>
+#include <linux/clk.h>
+
+struct clk {
+ struct kobject kobj;
+
+ int usage;
+
+ const struct clk_ops *ops;
+ void *data;
+};
+
+
+static DEFINE_SPINLOCK(clocks_lock);
+
+static void clk_release(struct kobject *kobj)
+{
+ struct clk *clk = container_of(kobj, struct clk, kobj);
+
+ kfree(clk);
+}
+
+static ssize_t clk_usage_show(struct kobject *kobj,
+ struct kobj_attribute *attr,
+ char *buf)
+{
+ struct clk *clk = container_of(kobj, struct clk, kobj);
+ unsigned long flags;
+ int val;
+
+ spin_lock_irqsave(&clocks_lock, flags);
+
+ val = clk->usage;
+
+ spin_unlock_irqrestore(&clocks_lock, flags);
+
+ return snprintf(buf, PAGE_SIZE, "%d", val);
+}
+
+static struct kobj_attribute clk_usage_attr =
+ __ATTR(usage, 0444, clk_usage_show, NULL);
+
+static ssize_t clk_rate_show(struct kobject *kobj,
+ struct kobj_attribute *attr,
+ char *buf)
+{
+ return snprintf(buf, PAGE_SIZE, "%lu",
+ clk_get_rate(container_of(kobj, struct clk, kobj)));
+}
+
+static struct kobj_attribute clk_rate_attr =
+ __ATTR(rate, 0444, clk_rate_show, NULL);
+
+
+static struct attribute *clk_attrs[] = {
+ &clk_usage_attr.attr,
+ &clk_rate_attr.attr,
+ NULL,
+};
+
+static struct kobj_type clk_ktype = {
+ .release = clk_release,
+ .sysfs_ops = &kobj_sysfs_ops,
+ .default_attrs = clk_attrs,
+};
+
+static struct kset *clks_kset;
+
+static int clk_sysfs_init(void)
+{
+ clks_kset = kset_create_and_add("clocks", NULL, kernel_kobj);
+
+ if (!clks_kset)
+ return -ENOMEM;
+
+ return 0;
+}
+core_initcall(clk_sysfs_init);
+
+static inline struct clk *__clk_get_parent(struct clk *clk)
+{
+ return clk->kobj.parent == &clks_kset->kobj ?
+ NULL :
+ container_of(kobject_get(clk->kobj.parent), struct clk, kobj);
+}
+
+struct clk *clk_get_parent(struct clk *clk)
+{
+ unsigned long flags;
+ struct clk *parent;
+
+ spin_lock_irqsave(&clocks_lock, flags);
+
+ parent = __clk_get_parent(clk);
+
+ spin_unlock_irqrestore(&clocks_lock, flags);
+
+ return parent;
+}
+
+
+struct clk *clk_alloc(struct clk *parent, const struct clk_ops *ops,
+ void * data, const char *fmt, ...)
+{
+ struct clk *clk = kzalloc(sizeof(*clk), GFP_ATOMIC);
+ char *name;
+ va_list args;
+ int rc;
+
+ if (!clk)
+ return ERR_PTR(-ENOMEM);
+
+ clk->kobj.kset = clks_kset;
+
+ clk->ops = ops;
+ clk->data = data;
+
+ va_start(args, fmt);
+ name = kvasprintf(GFP_KERNEL, fmt, args);
+ va_end(args);
+ if (!name) {
+ kfree(clk);
+ return ERR_PTR(-ENOMEM);
+ }
+
+ rc = kobject_init_and_add(&clk->kobj, &clk_ktype,
+ parent? &parent->kobj : NULL,
+ "%s", name);
+
+ kfree(name);
+
+ if (rc) {
+ kfree(clk);
+ clk = ERR_PTR(rc);
+ }
+
+ return clk;
+}
+EXPORT_SYMBOL(clk_alloc);
+
+void clk_free(struct clk *clk)
+{
+ unsigned long flags;
+ spin_lock_irqsave(&clocks_lock, flags);
+
+ clk->ops = NULL;
+ clk->data = NULL;
+
+ kobject_put(&clk->kobj);
+
+ spin_unlock_irqrestore(&clocks_lock, flags);
+}
+EXPORT_SYMBOL(clk_free);
+
+int clks_register(struct clk_table *clks, int size)
+{
+ int i;
+ int rc = 0;
+ for (i = 0; i < size; i++) {
+ struct clk *parent = NULL;
+ struct clk *clk;
+ if (clks[i].parent_name) {
+ parent = clk_get(clks[i].parent_dev, clks[i].parent_name);
+ if (!parent) {
+ rc = -ENOENT;
+ i--;
+ break;
+ }
+ }
+ clk = clk_alloc(parent, clks[i].ops,
+ clks[i].data, "%s", clks[i].name);
+ if (parent)
+ clk_put(parent);
+ if (IS_ERR(clk)) {
+ rc = PTR_ERR(clk);
+ i --;
+ break;
+ }
+
+ clks[i].clk = clk;
+ }
+
+ if (rc) {
+ for ( ; i >= 0; i--) {
+ clk_free(clks[i].clk);
+ clks[i].clk = NULL;
+ }
+ }
+
+ return rc;
+}
+EXPORT_SYMBOL(clks_register);
+
+
+struct clk *clk_get(struct device *dev, const char *name)
+{
+ struct clk *clk = ERR_PTR(-ENOENT);
+ struct clk *p;
+ unsigned long flags;
+ struct kobject *k;
+
+ /* Take both clocks_lock and kset lock */
+ spin_lock_irqsave(&clocks_lock, flags);
+ spin_lock(&clks_kset->list_lock);
+
+ list_for_each_entry(k, &clks_kset->list, entry) {
+ if (kobject_name(k) && !strcmp(kobject_name(k), name)
+ ) {
+ p = container_of(kobject_get(k), struct clk, kobj);
+ if (p->ops && p->ops->can_get &&
+ p->ops->can_get(p, p->data, dev)) {
+ clk = p;
+ break;
+ }
+ kobject_put(k);
+ }
+ }
+
+ list_for_each_entry(k, &clks_kset->list, entry) {
+ if (kobject_name(k) && !strcmp(kobject_name(k), name)
+ ) {
+ p = container_of(kobject_get(k), struct clk, kobj);
+ if (!p->ops || !p->ops->can_get) {
+ clk = p;
+ break;
+ }
+ kobject_put(k);
+ break;
+ }
+ }
+
+ spin_unlock(&clks_kset->list_lock);
+ spin_unlock_irqrestore(&clocks_lock, flags);
+
+ return clk;
+}
+EXPORT_SYMBOL(clk_get);
+
+void clk_put(struct clk *clk)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&clocks_lock, flags);
+
+ kobject_put(&clk->kobj);
+
+ spin_unlock_irqrestore(&clocks_lock, flags);
+}
+EXPORT_SYMBOL(clk_put);
+
+static int __clk_enable(struct clk *clk)
+{
+ int rc;
+
+ if (clk->usage ++ != 0)
+ return 0;
+
+ if (!clk->ops || !clk->ops->set_mode)
+ return 0;
+
+ rc = clk->ops->set_mode(clk, clk->data, true);
+ if (rc) {
+ clk->ops->set_mode(clk, clk->data, false);
+ clk->usage --;
+ }
+
+ return rc;
+}
+
+int clk_enable(struct clk *clk)
+{
+ unsigned long flags;
+ int rc = 0;
+
+ spin_lock_irqsave(&clocks_lock, flags);
+
+ rc = __clk_enable(clk);
+
+ spin_unlock_irqrestore(&clocks_lock, flags);
+ return rc;
+}
+EXPORT_SYMBOL(clk_enable);
+
+static int __clk_disable(struct clk *clk)
+{
+ clk->usage --;
+
+ WARN_ON(clk->usage < 0);
+
+ if (clk->usage != 0)
+ return 0;
+
+ if (!clk->ops || !clk->ops->set_mode)
+ return 0;
+
+ return clk->ops->set_mode(clk, clk->data, false);
+}
+
+void clk_disable(struct clk *clk)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&clocks_lock, flags);
+
+ __clk_disable(clk);
+
+ spin_unlock_irqrestore(&clocks_lock, flags);
+}
+EXPORT_SYMBOL(clk_disable);
+
+unsigned long clk_get_rate(struct clk *clk)
+{
+ unsigned long flags;
+
+ unsigned long rate = 0;
+
+ spin_lock_irqsave(&clocks_lock, flags);
+
+ if (!clk->ops || !clk->ops->get_rate)
+ goto out;
+
+ rate = clk->ops->get_rate(clk, clk->data);
+
+out:
+ spin_unlock_irqrestore(&clocks_lock, flags);
+
+ return rate;
+}
+EXPORT_SYMBOL(clk_get_rate);
+
+int clk_set_rate(struct clk *clk, unsigned long rate)
+{
+ unsigned long flags;
+ long rc = -EINVAL;
+
+ spin_lock_irqsave(&clocks_lock, flags);
+
+ if (!clk->ops || !clk->ops->round_rate)
+ goto out;
+
+ rc = clk->ops->round_rate(clk, clk->data, rate, true);
+
+out:
+ spin_unlock_irqrestore(&clocks_lock, flags);
+
+ return rc < 0 ? rc : 0;
+}
+EXPORT_SYMBOL(clk_set_rate);
+
+long clk_round_rate(struct clk *clk, unsigned long rate)
+{
+ unsigned long flags;
+ long rc = -EINVAL;
+
+ spin_lock_irqsave(&clocks_lock, flags);
+
+ if (!clk->ops || !clk->ops->round_rate)
+ goto out;
+
+ rc = clk->ops->round_rate(clk, clk->data, rate, false);
+
+out:
+ spin_unlock_irqrestore(&clocks_lock, flags);
+
+ return rc;
+}
+EXPORT_SYMBOL(clk_round_rate);
+
+
+static int clk_dev_can_get(struct clk *clk, void *data, struct device *dev)
+{
+ return data == dev;
+}
+
+static int clk_set_mode_parent(struct clk *clk, void *data, bool enable)
+{
+ struct clk *parent = __clk_get_parent(clk);
+ int rc;
+
+ printk("!!! %sable: %s (%s)\n", enable? "en": "dis",
+ kobject_name(&clk->kobj),
+ kobject_name(&parent->kobj));
+
+ BUG_ON(!parent);
+
+ if (enable)
+ rc = __clk_enable(parent);
+ else
+ rc = __clk_disable(parent);
+
+ clk_put(parent);
+
+ return rc;
+}
+
+static unsigned long clk_get_rate_parent(struct clk *clk, void *data)
+{
+ struct clk *parent = __clk_get_parent(clk);
+ unsigned long rate = 0;
+
+ BUG_ON(!parent);
+
+ if (!parent->ops || !parent->ops->get_rate)
+ WARN_ON(1);
+ else
+ rate = parent->ops->get_rate(parent, parent->data);
+
+ clk_put(parent);
+
+ return rate;
+}
+
+const struct clk_ops clk_dev_ops = {
+ .can_get = clk_dev_can_get,
+ .set_mode = clk_set_mode_parent,
+ .get_rate = clk_get_rate_parent,
+};
+EXPORT_SYMBOL(clk_dev_ops);
--
1.5.5.1
--
With best wishes
Dmitry
--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/