[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-ID: <20080608092156.GA11474@doriath.ww600.siemens.net>
Date: Sun, 8 Jun 2008 13:21:56 +0400
From: Dmitry Baryshkov <dbaryshkov@...il.com>
To: linux-kernel@...r.kernel.org
Cc: akpm@...ux-foundation.org,
Haavard Skinnemoen <haavard.skinnemoen@...el.com>,
Russell King <rmk+lkml@....linux.org.uk>,
Paul Mundt <lethal@...ux-sh.org>,
pHilipp Zabel <philipp.zabel@...il.com>,
Pavel Machek <pavel@....cz>, tony@...mide.com, paul@...an.com,
David Brownell <david-b@...bell.net>,
Mark Brown <broonie@...nsource.wolfsonmicro.com>
Subject: [RFC][PATCH 1/3] Clocklib: add generic framework for managing
clocks.
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@...il.com>
---
include/linux/clocklib.h | 91 +++++++++++++
lib/Kconfig | 3 +
lib/Makefile | 1 +
lib/clocklib.c | 318 ++++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 413 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..c1da052
--- /dev/null
+++ b/include/linux/clocklib.h
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2008 Dmitry Baryshkov
+ *
+ * This file is released under the GPL v2.
+ */
+#ifndef CLOCKLIB_H
+#define CLOCKLIB_H
+
+struct device;
+/*
+ * @kobj: kobject representing a clock
+ * @state: represents the state of the clock. it's -1, when the clock is locked otherwise it shows the "enable" counter
+ * @ops: a reference to implementation of operations for clock
+ * @release: deallocate all resources after last reference to this clock was removed
+ */
+struct clk {
+ const char *name;
+ struct kobject kobj;
+
+ atomic_t state;
+
+ const struct clk_ops *ops;
+ void (*release)(struct clk *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
+ * @enable: enable specified clock.
+ * @disable: disable specified clock.
+ * @get_rate: obtain the current rate of a specified clock in Hz
+ * @round_rate: adjust a rate to the exact value a clock can provide and possibly apply it
+ *
+ * This structure specifies clock operations that are used to configure
+ * specific clock. All functions are called with spin lock held,
+ * so you can't do fancy things. However you can use helpers for accessing
+ * parent clocks.
+ *
+ * The enable, disable, get_rate and round_rate fields are mandatory.
+ *
+ * can_get is optional. It checks whether this particular "struct clk" is
+ * suitable for passed device. E.g. for the uniformity of the driver the
+ * PXA names all clocks "UARTCLK" and binds each of them to the particular
+ * UART device in the system. Some other platforms would like to compare
+ * the id of the device on the platform_bus with the value stored in the
+ * clock. To make this check generic, I've provided the possibility for
+ * each particular clock to define it's own way to be bound to devices.
+ */
+struct clk_ops {
+ int (*can_get)(struct clk *clk, struct device *dev);
+ int (*set_parent)(struct clk *clk, struct clk *parent);
+ int (*enable)(struct clk *clk);
+ void (*disable)(struct clk *clk);
+ unsigned long (*get_rate)(struct clk *clk);
+ long (*round_rate)(struct clk *clk, unsigned long hz, bool apply);
+};
+
+
+/*
+ * Add a clock to system. As the the struct clk embedds a kobject,
+ * in most cases you should allocate it dynamically. Allocate it
+ * from static space only if you know what you are doing.
+ */
+int clk_add(struct clk *parent, struct clk *clk);
+
+/*
+ * This unregisters the clock. However some other drivers can still
+ * reference the clk, so you have to take measures not to cause a BUG.
+ */
+void clk_unregister(struct clk *clk);
+
+
+/*
+ * Some useful helpers
+ */
+
+/*
+ * A simple clk wrapper and operations for it. This clock will be bound
+ * to the provided device.
+ */
+struct clk_devck {
+ struct clk clk;
+ const char *parent;
+ struct device *dev;
+};
+extern struct clk_ops clk_devck_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..bb3859a
--- /dev/null
+++ b/lib/clocklib.c
@@ -0,0 +1,318 @@
+/*
+ * 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>
+
+#include <linux/sched.h>
+#include <linux/wait.h>
+
+static void clk_release(struct kobject *kobj)
+{
+ struct clk *clk = container_of(kobj, struct clk, kobj);
+
+ BUG_ON(!clk->release);
+ clk->release(clk);
+}
+
+static ssize_t clk_state_show(struct kobject *kobj,
+ struct kobj_attribute *attr,
+ char *buf)
+{
+ struct clk *clk = container_of(kobj, struct clk, kobj);
+ int val;
+
+ smp_mb();
+ val = atomic_read(&clk->state);
+
+ return snprintf(buf, PAGE_SIZE, "%d", val);
+}
+
+static struct kobj_attribute clk_state_attr =
+ __ATTR(state, 0444, clk_state_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_state_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);
+
+int clk_add(struct clk *parent, struct clk *clk)
+{
+ BUG_ON(!clk->ops);
+ BUG_ON(!clk->release);
+ BUG_ON(!clk->name);
+
+ BUG_ON(!clk->ops->enable || !clk->ops->disable ||
+ !clk->ops->get_rate || !clk->ops->round_rate);
+
+ kobject_init(&clk->kobj, &clk_ktype);
+
+ clk->kobj.kset = clks_kset;
+
+ return kobject_add(&clk->kobj, parent? &parent->kobj : NULL,
+ "%s", clk->name);
+}
+
+void clk_unregister(struct clk *clk)
+{
+ kobject_del(&clk->kobj);
+ kobject_put(&clk->kobj);
+}
+
+struct clk *clk_get_parent(struct clk *clk)
+{
+ struct clk *parent;
+
+ spin_lock(&clks_kset->list_lock);
+
+ parent = clk->kobj.parent == &clks_kset->kobj ?
+ NULL :
+ container_of(kobject_get(clk->kobj.parent), struct clk, kobj);
+
+ spin_unlock(&clks_kset->list_lock);
+
+ return parent;
+}
+
+struct clk *clk_get(struct device *dev, const char *name)
+{
+ struct clk *clk = NULL;
+ struct clk *p;
+ struct kobject *k;
+
+ 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, 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);
+
+ return clk;
+}
+EXPORT_SYMBOL(clk_get);
+
+void clk_put(struct clk *clk)
+{
+ kobject_put(&clk->kobj);
+}
+EXPORT_SYMBOL(clk_put);
+
+static DECLARE_WAIT_QUEUE_HEAD(clks_enable);
+
+static int __clk_get(struct clk *clk)
+{
+ int rc = 0;
+ int state;
+
+ /* FIXME: handle the case if we are in atomic context */
+ rc = wait_event_interruptible(clks_enable,
+ (state = atomic_xchg(&clk->state, -1)) >= 0);
+ if (rc < 0)
+ return rc;
+
+ return state;
+}
+
+static void __clk_put(struct clk *clk, int state)
+{
+ atomic_xchg(&clk->state, state);
+
+ wake_up(&clks_enable);
+}
+
+int clk_enable(struct clk *clk)
+{
+ int rc = 0;
+ int state = __clk_get(clk);
+
+ if (state < 0)
+ return state;
+ else if (state == 0)
+ rc = clk->ops->enable(clk);
+
+ __clk_put(clk, state + 1);
+
+ return rc;
+}
+EXPORT_SYMBOL(clk_enable);
+
+void clk_disable(struct clk *clk)
+{
+ int state = __clk_get(clk);
+
+ if (state < 0)
+ return;
+
+ if (state == 1)
+ clk->ops->disable(clk);
+
+ __clk_put(clk, state - 1);
+
+ return;
+}
+EXPORT_SYMBOL(clk_disable);
+
+unsigned long clk_get_rate(struct clk *clk)
+{
+ int state = __clk_get(clk);
+ unsigned long hz;
+
+ if (state < 0)
+ return 0;
+
+ hz = clk->ops->get_rate(clk);
+
+ __clk_put(clk, state);
+
+ return hz;
+}
+EXPORT_SYMBOL(clk_get_rate);
+
+int clk_set_rate(struct clk *clk, unsigned long hz)
+{
+ int rc;
+ int state = __clk_get(clk);
+
+ if (state < 0)
+ return state;
+
+ rc = clk->ops->round_rate(clk, hz, true);
+
+ __clk_put(clk, state);
+
+ return rc < 0 ? rc : 0;
+}
+EXPORT_SYMBOL(clk_set_rate);
+
+long clk_round_rate(struct clk *clk, unsigned long hz)
+{
+ long rc;
+ int state = __clk_get(clk);
+
+ if (state < 0)
+ return state;
+
+ rc = clk->ops->round_rate(clk, hz, false);
+
+ __clk_put(clk, state);
+
+ return rc;
+}
+EXPORT_SYMBOL(clk_round_rate);
+
+/*
+ * Some usefull helpers
+ */
+static inline int clk_enable_parent(struct clk *clk)
+{
+ struct clk *parent = clk_get_parent(clk);
+ int rc = clk_enable(parent);
+ clk_put(parent);
+ return rc;
+}
+
+static inline void clk_disable_parent(struct clk *clk)
+{
+ struct clk *parent = clk_get_parent(clk);
+ clk_disable(parent);
+ clk_put(parent);
+}
+
+static inline unsigned long clk_get_rate_parent(struct clk *clk)
+{
+ struct clk *parent = clk_get_parent(clk);
+ unsigned long rc = clk_get_rate(parent);
+ clk_put(parent);
+ return rc;
+}
+
+static inline long clk_round_rate_parent(struct clk *clk, unsigned long hz, bool apply)
+{
+ struct clk *parent = clk_get_parent(clk);
+ long rc = apply ? clk_set_rate(parent, hz) : clk_round_rate(parent, hz);
+ clk_put(parent);
+ return rc;
+}
+
+static inline int clk_devck_can_get(struct clk *clk, struct device *dev)
+{
+ struct clk_devck *dc = container_of(clk, struct clk_devck, clk);
+
+ return dc->dev == dev;
+}
+
+struct clk_ops clk_devck_ops = {
+ .can_get = clk_devck_can_get,
+ .enable = clk_enable_parent,
+ .disable = clk_disable_parent,
+ .get_rate = clk_get_rate_parent,
+ .round_rate = clk_round_rate_parent,
+};
+EXPORT_SYMBOL(clk_devck_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@...r.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/
Powered by blists - more mailing lists