[PATCH] generic clock framework
Francesco Virlinzi
francesco.virlinzi at st.com
Fri Oct 23 09:26:42 EDT 2009
version: 0.6.2
Signed-off-by: Francesco Virlinzi <francesco.virlinzi at st.com>
---
drivers/base/Makefile | 4 +
drivers/base/base.h | 5 +
drivers/base/clk.c | 1606 +++++++++++++++++++++++++++++++++++++++
drivers/base/clk.h | 319 ++++++++
drivers/base/clk_pm.c | 197 +++++
drivers/base/clk_utils.c | 456 +++++++++++
drivers/base/init.c | 1 +
drivers/base/platform.c | 27 +
include/linux/clk.h | 251 ++++++
include/linux/platform_device.h | 9 +
init/Kconfig | 23 +
11 files changed, 2898 insertions(+), 0 deletions(-)
create mode 100644 drivers/base/clk.c
create mode 100644 drivers/base/clk.h
create mode 100644 drivers/base/clk_pm.c
create mode 100644 drivers/base/clk_utils.c
diff --git a/drivers/base/Makefile b/drivers/base/Makefile
index b5b8ba5..b78a2bf 100644
--- a/drivers/base/Makefile
+++ b/drivers/base/Makefile
@@ -16,6 +16,10 @@ ifeq ($(CONFIG_SYSFS),y)
obj-$(CONFIG_MODULES) += module.o
endif
obj-$(CONFIG_SYS_HYPERVISOR) += hypervisor.o
+ifdef CONFIG_GENERIC_CLK_FM
+obj-y += clk.o clk_utils.o
+obj-$(CONFIG_PM) += clk_pm.o
+endif
ifeq ($(CONFIG_DEBUG_DRIVER),y)
EXTRA_CFLAGS += -DDEBUG
diff --git a/drivers/base/base.h b/drivers/base/base.h
index b528145..bc5b9e8 100644
--- a/drivers/base/base.h
+++ b/drivers/base/base.h
@@ -94,6 +94,11 @@ extern int devices_init(void);
extern int buses_init(void);
extern int classes_init(void);
extern int firmware_init(void);
+#ifdef CONFIG_GENERIC_CLK_FM
+extern int clock_init(void);
+#else
+static inline int clock_init(void){ return 0; }
+#endif
#ifdef CONFIG_SYS_HYPERVISOR
extern int hypervisor_init(void);
#else
diff --git a/drivers/base/clk.c b/drivers/base/clk.c
new file mode 100644
index 0000000..7feae61
--- /dev/null
+++ b/drivers/base/clk.c
@@ -0,0 +1,1606 @@
+/*
+ * -------------------------------------------------------------------------
+ * clk.c
+ * -------------------------------------------------------------------------
+ * (C) STMicroelectronics 2008
+ * (C) STMicroelectronics 2009
+ * Author: Francesco M. Virlinzi <francesco.virlinzi at st.com>
+ * -------------------------------------------------------------------------
+ * May be copied or modified under the terms of the GNU General Public
+ * License v.2 ONLY. See linux/COPYING for more information.
+ *
+ * -------------------------------------------------------------------------
+ */
+
+#include <linux/platform_device.h>
+#include <linux/clk.h>
+#include <linux/klist.h>
+#include <linux/sysdev.h>
+#include <linux/kref.h>
+#include <linux/kobject.h>
+#include <linux/err.h>
+#include <linux/spinlock.h>
+#include <asm/atomic.h>
+#include "clk.h"
+#include "base.h"
+
+#define CLK_NAME "Generic Clk Framework"
+#define CLK_VERSION "0.6.2"
+
+/* #define CLK_SAFE_CODE */
+
+klist_entry_support(clock, clk, node)
+klist_entry_support(child_clock, clk, child_node)
+klist_entry_support(dev_info, pdev_clk_info, node)
+
+#define to_clk(ptr) container_of(ptr, struct clk, kobj)
+#define to_tnode(ptr) container_of(ptr, struct clk_tnode, pnode)
+
+static int sysfs_clk_attr_show(struct kobject *kobj,
+ struct attribute *attr, char *buf)
+{
+ ssize_t ret = -EIO;
+ struct kobj_attribute *kattr
+ = container_of(attr, struct kobj_attribute, attr);
+ if (kattr->show)
+ ret = kattr->show(kobj, kattr, buf);
+ return ret;
+}
+
+static ssize_t
+sysfs_clk_attr_store(struct kobject *kobj, struct attribute *attr,
+ const char *buf, size_t count)
+{
+ ssize_t ret = -EIO;
+ struct kobj_attribute *kattr
+ = container_of(attr, struct kobj_attribute, attr);
+ if (kattr->store)
+ ret = kattr->store(kobj, kattr, buf, count);
+ return ret;
+}
+
+static struct sysfs_ops clk_sysfs_ops = {
+ .show = sysfs_clk_attr_show,
+ .store = sysfs_clk_attr_store,
+};
+
+static struct kobj_type ktype_clk = {
+ .sysfs_ops = &clk_sysfs_ops,
+};
+
+static struct clk *check_clk(struct clk *);
+
+static struct kobject *clk_kobj;
+static DEFINE_MUTEX(clk_list_sem);
+static atomic_t transaction_counter = ATOMIC_INIT(0);
+struct klist clk_list = KLIST_INIT(clk_list, NULL, NULL);
+
+klist_function_support(child, clk, child_node, kobj)
+klist_function_support(device, pdev_clk_info, node, pdev->dev.kobj)
+
+/*
+ * The ___clk_xxx operations doesn't raise propagation
+ * they are used to operate on the real clock
+ */
+static int
+__clk_operations(struct clk *clk, unsigned long rate,
+ enum clk_ops_id const id_ops)
+{
+ int ret = 0;
+ unsigned long *ops_fns = (unsigned long *)clk->ops;
+ if (likely(ops_fns && ops_fns[id_ops])) {
+ int (*fns)(struct clk *clk, unsigned long rate)
+ = (void *)ops_fns[id_ops];
+ unsigned long flags;
+ spin_lock_irqsave(&clk->lock, flags);
+ ret = fns(clk, rate);
+ spin_unlock_irqrestore(&clk->lock, flags);
+ }
+ return ret;
+}
+
+static inline int __clk_init(struct clk *clk)
+{
+ pr_debug(": %s\n", clk->name);
+ return __clk_operations(clk, 0, __CLK_INIT);
+}
+static inline int __clk_enable(struct clk *clk)
+{
+ pr_debug(": %s\n", clk->name);
+ return __clk_operations(clk, 0, __CLK_ENABLE);
+}
+static inline int __clk_disable(struct clk *clk)
+{
+ pr_debug(": %s\n", clk->name);
+ return __clk_operations(clk, 0, __CLK_DISABLE);
+}
+static inline int __clk_set_rate(struct clk *clk, unsigned long rate)
+{
+ pr_debug(": %s\n", clk->name);
+ return __clk_operations(clk, rate, __CLK_SET_RATE);
+}
+static inline int __clk_set_parent(struct clk *clk, struct clk *parent)
+{
+ pr_debug(": %s\n", clk->name);
+ return __clk_operations(clk, (unsigned long)parent, __CLK_SET_PARENT);
+}
+static inline int __clk_recalc_rate(struct clk *clk)
+{
+ pr_debug(": %s\n", clk->name);
+ return __clk_operations(clk, 0, __CLK_RECALC);
+}
+static inline int __clk_round(struct clk *clk, unsigned long value)
+{
+ pr_debug(": %s\n", clk->name);
+ return __clk_operations(clk, value, __CLK_ROUND);
+}
+
+static inline int __clk_eval(struct clk *clk, unsigned long prate)
+{
+#ifndef CONFIG_CLK_FORCE_GENERIC_EVALUATE
+ pr_debug(": %s\n", clk->name);
+ return __clk_operations(clk, prate, __CLK_EVAL);
+#else
+ unsigned long rate, flags;
+ pr_debug(": %s\n", clk->name);
+ if (likely(clk->ops && clk->ops->eval)) {
+ spin_lock_irqsave(&clk->lock, flags);
+ rate = clk->ops->eval(clk, prate);
+ spin_unlock_irqrestore(&clk->lock, flags);
+ } else
+ rate = clk_generic_evaluate_rate(clk, prate);
+ return rate;
+#endif
+}
+
+#ifdef CONFIG_PM_RUNTIME
+static int
+clk_pm_runtime_devinfo(enum rpm_status code, struct pdev_clk_info *info)
+{
+ struct platform_device *pdev = info->pdev;
+
+ pr_debug("\n");
+
+ switch (code) {
+ case RPM_ACTIVE:
+ return clk_notify_child_event(CHILD_DEVICE_ENABLED, info->clk);
+ case RPM_SUSPENDED:
+ return clk_notify_child_event(CHILD_DEVICE_DISABLED, info->clk);
+ }
+ return -EINVAL;
+}
+
+int clk_pm_runtime_device(enum rpm_status code, struct platform_device *dev)
+{
+ int idx;
+ int ret = 0;
+ struct pdev_clk_info *info;
+
+ if (!dev)
+ return -EFAULT;
+
+ if (!dev->clks || !pdevice_num_clocks(dev))
+ return 0;
+
+ pr_debug("\n");
+/*
+ * Check if the device is under a transaction.
+ * If so the GCFdoesn't raise a 'clk_pm_runtime_devinfo'
+ * all the device change will be notified on 'tnode_transaction_complete'
+ * if required....
+ */
+ if (atomic_read((atomic_t *)&dev->clk_flags)) {
+ pr_debug("%s.%d under transaction\n", dev->name, dev->id);
+ return ret;
+ }
+ for (idx = 0, info = dev->clks; idx < pdevice_num_clocks(dev); ++idx)
+ ret |= clk_pm_runtime_devinfo(&info[idx], state, 0);
+
+ return ret;
+}
+#else
+#define clk_pm_runtime_devinfo(x, y)
+#define clk_pm_runtime_device(x, y)
+#endif
+
+/**
+ * tnode_malloc
+ *
+ * Allocs the memory for both the transaction and the
+ * clk_event objects
+ */
+static struct clk_tnode *tnode_malloc(struct clk_tnode *parent,
+ unsigned long nevent)
+{
+ struct clk_event *evt;
+ struct clk_tnode *node;
+
+ if (nevent > 32)
+ return NULL;
+
+ node = kmalloc(sizeof(*node) + nevent * sizeof(*evt), GFP_KERNEL);
+
+ if (!node)
+ return NULL;
+
+ evt = (struct clk_event *)(sizeof(struct clk_tnode) + (long)node);
+
+ node->tid = atomic_inc_return(&transaction_counter);
+ node->parent = parent;
+ node->size = nevent;
+ node->events = evt;
+ node->events_map = 0;
+ INIT_LIST_HEAD(&node->childs);
+
+ return node;
+}
+
+/**
+ * tnode_free
+ *
+ * Free the tnode memory
+ */
+static void tnode_free(struct clk_tnode *node)
+{
+ if (tnode_get_parent(node)) {
+ list_del(&node->pnode);
+ kfree(node);
+ }
+}
+
+/**
+ * tnode_check_clock -
+ *
+ * @node: the tnode object
+ * @clk: the clock object
+ *
+ * returns a boolean value
+ * it checks if the clock (clk) is managed by the
+ * tnode (node) or any parent node
+ */
+static int __must_check
+tnode_check_clock(struct clk_tnode *node, struct clk *clk)
+{
+ int j;
+ for (; node; node = tnode_get_parent(node))
+ /* scans all the event */
+ tnode_for_each_valid_events(node, j)
+ if (tnode_get_clock(node, j) == clk)
+ return 1; /* FOUND!!! */
+ return 0;
+}
+
+/**
+ * tnode_lock_clocks -
+ *
+ * @node: the tnode object
+ *
+ * marks all the clocks under transaction to be sure there is no more
+ * than one transaction for each clock
+ */
+static int __must_check
+tnode_lock_clocks(struct clk_tnode *node)
+{
+ int i;
+ pr_debug("\n");
+
+ /* 1. try to mark all the clocks in transaction */
+ for (i = 0; i < tnode_get_size(node); ++i)
+ if (clk_set_towner(tnode_get_clock(node, i), node)) {
+ struct clk *clkp = tnode_get_clock(node, i);
+ /* this clock is already locked */
+ /* we accept that __only__ if it is locked by a
+ * parent tnode!!!
+ */
+ if (!tnode_get_parent(node)) {
+ pr_debug("Error clk %s locked but "
+ "there is no parent!\n", clkp->name);
+ goto err_0;
+ }
+ pr_debug("clk %s already locked\n", clkp->name);
+ if (tnode_check_clock(tnode_get_parent(node), clkp)) {
+ pr_debug("ok clk %s locked "
+ "by a parent\n", clkp->name);
+ continue;
+ } else
+ goto err_0;
+ } else
+ /* set the event as valid in the bitmap*/
+ tnode_set_map_id(node, i);
+
+/*
+ * all the clocks are marked succesfully or all the clock on
+ * this tnode are already managed by parent
+ */
+ if (!tnode_get_map(node)) { /* check if the bitamp is not zero */
+ if (tnode_get_parent(node))
+ kfree(node);
+ return 1;
+ }
+
+ /*
+ * all the clocks are marked succesfully _and_ there is at least
+ * one clock marked.
+ * Add the tnode to its parent! and return
+ */
+ if (tnode_get_parent(node))
+ list_add_tail(&node->pnode, &tnode_get_parent(node)->childs);
+
+ return 0;
+
+err_0:
+ pr_debug("Error on clock locking...\n");
+ for (--i; i >= 0; --i)
+ if (tnode_check_map_id(node, i))
+ clk_clean_towner(tnode_get_clock(node, i));
+
+ if (tnode_get_parent(node))
+ kfree(node);
+
+ return -EINVAL;
+}
+
+/**
+ * tnode_transaction_complete -
+ *
+ * checks the devices status when the transaction is complete.
+ */
+static void tnode_transaction_complete(struct clk_tnode *node)
+{
+ struct klist_iter i;
+ struct pdev_clk_info *dev_info;
+ int j;
+
+ pr_debug("tid: %d\n", (int)tnode_get_id(node));
+ tnode_for_each_valid_events(node, j) {
+ klist_iter_init(&tnode_get_clock(node, j)->devices, &i);
+ while ((dev_info = next_dev_info(&i))) {
+ /* update the device state */
+ struct platform_device *dev = dev_info->pdev;
+ switch (dev->clk_state & (DEV_SUSPENDED_ON_TRANSACTION |
+ DEV_RESUMED_ON_TRANSACTION)) {
+ case 0: /* this device doesn't care on the clock transaction */
+ atomic_clear_mask(DEV_ON_TRANSACTION,
+ (atomic_t *)&dev->clk_state);
+ break;
+
+ case (DEV_SUSPENDED_ON_TRANSACTION |
+ DEV_RESUMED_ON_TRANSACTION):
+ /* this device was suspended and
+ * resumed therefore no real change
+ */
+ pr_debug("dev: %s.%d "
+ "Suspended&Resumed (no child event)\n",
+ dev->name, dev->id);
+ atomic_clear_mask(DEV_ON_TRANSACTION |
+ DEV_SUSPENDED_ON_TRANSACTION |
+ DEV_RESUMED_ON_TRANSACTION,
+ (atomic_t *)&dev->clk_state);
+ break;
+ case DEV_SUSPENDED_ON_TRANSACTION:
+ atomic_clear_mask(DEV_ON_TRANSACTION |
+ DEV_SUSPENDED_ON_TRANSACTION,
+ (atomic_t *)&dev->clk_state);
+ pr_debug("dev: %s.%d Suspended\n",
+ dev->name, dev->id);
+ clk_pm_runtime_device(RPM_SUSPENDED, dev);
+ break;
+ case DEV_RESUMED_ON_TRANSACTION:
+ atomic_clear_mask(DEV_ON_TRANSACTION |
+ DEV_RESUMED_ON_TRANSACTION,
+ (atomic_t *)&dev->clk_state);
+ pr_debug("dev: %s.%d Resumed\n",
+ dev->name, dev->id);
+ clk_pm_runtime_device(RPM_ACTIVE, dev);
+ break;
+
+ default:
+ printk(KERN_ERR "%s: device %s,%d clk_flags _not_ valid %u\n",
+ __func__, dev->name, dev->id,
+ (unsigned int)dev->clk_state);
+ }
+ }
+ klist_iter_exit(&i);
+ clk_clean_towner(tnode_get_clock(node, j));
+ }
+ pr_debug("tid: %d exit\n", (int)tnode_get_id(node));
+ return;
+}
+
+/*
+ * Check if the clk is registered
+ */
+#ifdef CLK_SAFE_CODE
+static struct clk *check_clk(struct clk *clk)
+{
+ struct clk *clkp;
+ struct clk *result = NULL;
+ struct klist_iter i;
+
+ pr_debug("\n");
+
+ klist_iter_init(&clk_list, &i);
+ while ((clkp = next_clock(&i)))
+ if (clk == clkp) {
+ result = clk;
+ break;
+ }
+ klist_iter_exit(&i);
+ return result;
+}
+#else
+static inline struct clk *check_clk(struct clk *clk)
+{
+ return clk;
+}
+#endif
+
+enum child_event_e {
+ CHILD_CLOCK_ENABLED = 1,
+ CHILD_CLOCK_DISABLED,
+ CHILD_DEVICE_ENABLED,
+ CHILD_DEVICE_DISABLED,
+};
+
+static int
+clk_notify_child_event(enum child_event_e const code, struct clk *clk)
+{
+ if (!clk)
+ return 0;
+
+ switch (code) {
+ case CHILD_CLOCK_ENABLED:
+ ++clk->nr_active_clocks;
+ break;
+ case CHILD_CLOCK_DISABLED:
+ --clk->nr_active_clocks;
+ break;
+ case CHILD_DEVICE_ENABLED:
+ ++clk->nr_active_devices;
+ break;
+ case CHILD_DEVICE_DISABLED:
+ --clk->nr_active_devices;
+ break;
+ }
+
+ if (clk_is_auto_switching(clk)) {
+ /*
+ * Check if there are still users
+ */
+ if (!clk->nr_active_devices && !clk->nr_active_clocks)
+ clk_disable(clk);
+ else if (!clk_get_rate(clk)) /* if off.. turn-on */
+ clk_enable(clk);
+ }
+
+ return 0;
+}
+
+/**
+ * clk_dev_events_malloc -
+ *
+ * builds a struct clk_event array (dev_event).
+ * the array size (how many elements) is based on device_num_clocks(dev)
+ * the contenets of each element is equal to:
+ * - the events array (if the idx-clock is under transaction)
+ * - the current clock setting if the idx-clock isn't under transaction
+ */
+static struct clk_event * __must_check
+clk_dev_events_malloc(struct platform_device const *dev)
+{
+ struct clk_event *dev_events;
+ struct clk_tnode *node;
+ int i, j;
+ pr_debug("\n");
+/*
+ * 1. simple case:
+ * - device_num_clocks(dev) = 1
+ */
+ if (pdevice_num_clocks(dev) == 1) {
+ node = (struct clk_tnode *)pdevice_clock(dev, 0)->towner;
+ for (i = 0; i < tnode_get_size(node); ++i)
+ if (tnode_get_clock(node, i) == pdevice_clock(dev, 0))
+ return tnode_get_event(node, i);
+ }
+/*
+ * 2. - device_num_clocks(dev) > 1
+ * GCF has to build a dedicated device events (devents) array
+ * for this device! sorted as the device registered it-self!
+ */
+ dev_events = kmalloc(sizeof(*dev_events) * pdevice_num_clocks(dev),
+ GFP_KERNEL);
+ if (!dev_events)
+ return NULL;
+
+ for (i = 0; i < pdevice_num_clocks(dev); ++i) {
+ node = (struct clk_tnode *)pdevice_clock(dev, i)->towner;
+ dev_events[i].clk = pdevice_clock(dev, i);
+ if (!node) {/* this means this clocs isn't under transaction */
+ dev_events[i].old_rate =
+ clk_get_rate(pdevice_clock(dev, i));
+ dev_events[i].new_rate =
+ clk_get_rate(pdevice_clock(dev, i));
+ continue;
+ }
+ /* search the right clk_event */
+ for (j = 0; tnode_get_clock(node, j) != pdevice_clock(dev, i);
+ ++j);
+
+ dev_events[i].old_rate = tnode_get_event(node, j)->old_rate;
+ dev_events[i].new_rate = tnode_get_event(node, j)->new_rate;
+ }
+ return dev_events;
+}
+
+/**
+ * clk_devents_free -
+ * free the devent allocated on the device dev.
+ */
+static inline void
+clk_dev_events_free(struct clk_event *dev_events, struct platform_device *dev)
+{
+ if (pdevice_num_clocks(dev) == 1)
+ return ;
+ kfree(dev_events);
+}
+
+/**
+ * clk_trnsc_fsm -
+ *
+ * propagate the transaction to all the childs
+ * each transaction has the following life-time:
+ *
+ * +---------------+
+ * | ENTER_CLK | The ENTER state only for clocks
+ * +---------------+ - acquires all the clock of the transaction
+ * | - builds the transaction graph
+ * | - for each clock generates a child transaction
+ * |
+ * +---------------------+
+ * | +---------------+ |
+ * | | ENTER_DEV | | The ENTER state only for devices
+ * | +---------------+ | - >> NOTIFY_CLK_ENTERCHANGE << notified
+ * | | | - - the device could refuse the operation
+ * | | |
+ * | +---------------+ |
+ * | | PRE_DEV | | The PRE state only devices
+ * | +---------------+ | - >> NOTIFY_CLK_PRECHANGE << notified
+ * | | | - - the device could be suspended
+ * +---------------------+
+ * |
+ * +---------------+
+ * | CHANGE_CLK | The CHANGE state only for clocks
+ * +---------------+ - updates all the physical clocks
+ * | and relative clk_event_s according to
+ * | the hw value.
+ * +---------------------+
+ * | | |
+ * | +---------------+ |
+ * | | POST_DEV | | The POST state only for devices
+ * | +---------------+ | - >> NOTIFY_CLK_POSTCHANGE << notified
+ * | | | - - the devices could be resumed
+ * | | |
+ * | +---------------+ |
+ * | | EXIT_DEV | | The EXIT state only for devices
+ * | +---------------+ | - >> NOTIFY_CLK_EXITCHANGE << notified
+ * | | | - - the devices is aware all the other
+ * +---------------------+ devices are resumed.
+ * |
+ * +---------------+
+ * | EXIT_CLK | The EXIT state only for clocks
+ * +---------------+ (to free all the memory)
+ * - Free all the allocated memory
+ *
+ */
+
+static enum notify_ret_e
+clk_trnsc_fsm(enum clk_fsm_e const code, struct clk_tnode *node)
+{
+ struct pdev_clk_info *dev_info;
+ struct clk_tnode *tchild;
+ struct klist_iter i;
+ int j;
+ enum notify_ret_e tmp, ret_notifier = NOTIFY_EVENT_HANDLED;
+
+#ifdef CONFIG_CLK_DEBUG
+ switch (code) {
+ case TRNSC_ENTER_CLOCK:
+ case TRNSC_ENTER_DEVICE:
+ printk(KERN_INFO "ENTER_%s ",
+ (code == TRNSC_ENTER_CLOCK ? "CLK" : "DEV"));
+ break;
+ case TRNSC_PRE_DEVICE:
+ printk(KERN_INFO "PRE_DEV ");
+ break;
+ case TRNSC_CHANGE_CLOCK:
+ printk(KERN_INFO "CHANGE_CLK ");
+ break;
+ case TRNSC_POST_DEVICE:
+ printk(KERN_INFO "POST_DEV ");
+ break;
+ case TRNSC_EXIT_DEVICE:
+ case TRNSC_EXIT_CLOCK:
+ printk(KERN_INFO "EXIT_%s ",
+ (code == TRNSC_EXIT_DEVICE ? "DEV" : "CLK"));
+ break;
+ }
+ printk(KERN_INFO"tid:%u ", (unsigned int)tnode_get_id(node));
+ if (tnode_get_parent(node))
+ printk(KERN_INFO " (tpid: %d)",
+ (int)tnode_get_id(tnode_get_parent(node)));
+ printk(KERN_INFO " (0x%x/0x%x) ", (unsigned int)tnode_get_size(node),
+ (unsigned int)tnode_get_map(node));
+ for (j = 0; j < tnode_get_size(node); ++j) {
+ if (tnode_check_map_id(node, j))
+ /* print only the valid event... */
+ printk(KERN_INFO"- %s ",
+ tnode_get_clock(node, j)->name);
+ else if (code == TRNSC_ENTER_CLOCK)
+ printk(KERN_INFO"- %s ",
+ tnode_get_clock(node, j)->name);
+ }
+ printk(KERN_INFO"\n");
+#endif
+
+ /*
+ * Clk ENTER state
+ */
+ if (code == TRNSC_ENTER_CLOCK) {
+ unsigned long idx;
+ enum clk_event_e sub_code;
+ struct clk *clkp;
+ struct clk_event *sub_event = NULL;
+
+ /* first of all the GCF tries to lock the clock of this tnode
+ * and links the tnode to its parent (if any)
+ */
+ switch (tnode_lock_clocks(node)) {
+ case 0:
+ break;
+ case -EINVAL:
+ return NOTIFY_EVENT_NOTHANDLED;
+ case 1:
+ return NOTIFY_EVENT_HANDLED;
+ }
+
+ pr_debug("clocks acquired\n");
+ /* Propagates the events to the sub clks */
+ tnode_for_each_valid_events(node, j) {
+
+ if (!clk_allow_propagation(tnode_get_clock(node, j))) {
+ pr_debug("clk: %s doesn't want propagation\n",
+ tnode_get_clock(node, j)->name);
+ continue;
+ }
+ if (!(tnode_get_clock(node, j)->nr_clocks))
+ continue;
+
+ tchild = tnode_malloc(node,
+ tnode_get_clock(node, j)->nr_clocks);
+ if (!tchild) {
+ printk(KERN_ERR "No enough memory during a clk "
+ "transaction\n");
+ ret_notifier |= NOTIFY_EVENT_NOTHANDLED;;
+ return ret_notifier;
+ }
+
+ pr_debug("memory for child transaction acquired\n");
+ idx = 0;
+ sub_code = clk_event_decode(tnode_get_event(node, j));
+ klist_iter_init(&tnode_get_clock(node, j)->childs, &i);
+ while ((clkp = next_child_clock(&i))) {
+ sub_event = tnode_get_event(tchild, idx);
+ clk_event_init(sub_event, clkp, clk_get_rate(clkp),
+ clk_get_rate(clkp));
+ switch (sub_code) {/* prepare the sub event fields */
+ case _CLK_CHANGE:
+ case _CLK_ENABLE:
+ sub_event->new_rate = clk_evaluate_rate(clkp,
+ tnode_get_event(node, j)->new_rate);
+ break;
+ case _CLK_DISABLE:
+ sub_event->new_rate = 0;
+ break;
+ case _CLK_NOCHANGE:
+ break;
+ }
+ ++idx;
+ }
+ klist_iter_exit(&i);
+ /* now GCF can araiese the sub transaction */
+ ret_notifier |=
+ clk_trnsc_fsm(code, tchild);
+ }
+ return ret_notifier;
+ }
+
+ /*
+ * Clk CHANGE state
+ */
+ if (code == TRNSC_CHANGE_CLOCK) {
+ /* the clocks on the root node are managed directly in the
+ * clk_set_rate/clk_enable/... functions ...
+ * while all the other clocks have to managed here!
+ */
+ if (node->parent)
+ tnode_for_each_valid_events(node, j) {
+ struct clk_event *event;
+ long code;
+ event = tnode_get_event(node, j);
+ code = clk_event_decode(event);
+ switch (code) {
+ case _CLK_CHANGE:
+ __clk_recalc_rate(event->clk);
+ event->new_rate =
+ clk_get_rate(event->clk);
+ break;
+ case _CLK_ENABLE:
+ if (clk_follow_parent(event->clk)) {
+ __clk_enable(event->clk);
+ event->new_rate =
+ clk_get_rate(event->clk);
+ }
+ break;
+ case _CLK_DISABLE:
+ if (clk_is_enabled(event->clk))
+ __clk_disable(event->clk);
+ break;
+ }
+ }
+
+ list_for_each_entry(tchild, &node->childs, pnode)
+ ret_notifier |= clk_trnsc_fsm(code, tchild);
+
+ return ret_notifier;
+ }
+
+ /*
+ * Clk EXIT state
+ */
+ if (code == TRNSC_EXIT_CLOCK) {
+ struct list_head *ptr, *next;
+ /* scans all the transaction childs */
+ list_for_each_safe(ptr, next, &node->childs)
+ clk_trnsc_fsm(code, to_tnode(ptr));
+
+ /* update the devices/clocks state */
+ tnode_transaction_complete(node);
+
+ tnode_free(node);
+ pr_debug("EXIT_CLK complete\n");
+
+ return ret_notifier;
+ }
+
+ /*
+ * Here the devices management
+ */
+ tnode_for_each_valid_events(node, j) {
+ if (!clk_allow_propagation(tnode_get_clock(node, j)))
+ continue;
+ klist_iter_init(&tnode_get_clock(node, j)->devices, &i);
+ while ((dev_info = next_dev_info(&i))) {
+ struct platform_device *pdev = dev_info->pdev;
+ struct platform_driver *pdrv = container_of(
+ pdev->dev.driver, struct platform_driver, driver);
+
+ struct clk_event *dev_events;
+
+ if (!pdrv || !pdrv->notify) {
+ pr_debug(
+ "device %s.%d registered with no notify function\n",
+ pdev->name, pdev->id);
+ continue;
+ }
+ /* check if it already had a 'code' event */
+ if (pdev_transaction_move_on(pdev, code))
+ continue;
+
+ dev_events = clk_dev_events_malloc(pdev);
+ if (!dev_events) {
+ printk(KERN_ERR"%s: No Memory during a clk "
+ "transaction\n", __func__);
+ continue;
+ }
+
+ /* GCF can use 'code' directly in the .notify function
+ * just because external 'NOTIFY_CLK_xxxCHANGE' code
+ * matchs with the internal 'device' code
+ */
+ tmp = pdrv->notify(code, pdev, dev_events);
+ clk_dev_events_free(dev_events, pdev);
+ ret_notifier |= tmp;
+#ifdef CONFIG_PM_RUNTIME
+ if (code == TRNSC_PRE_DEVICE && tmp == NOTIFY_EVENT_HANDLED) {
+ printk(KERN_INFO "clk %s on code %u suspends "
+ "device %s.%d\n",
+ transaction_get_clock(node, j)->name,
+ (unsigned int)code, pdev->name, pdev->id);
+ pm_runtime_suspend(&pdev->dev);
+ } else
+ if (code == TRNSC_POST_DEVICE && tmp == NOTIFY_EVENT_HANDLED) {
+ printk(KERN_INFO "clk %s on code %u resumes "
+ "device %s.%d\n",
+ transaction_get_clock(node, j)->name,
+ (unsigned int)code, pdev->name, pdev->id);
+ pm_runtime_resume(&pdev->dev);
+ };
+#endif
+ } /* while closed */
+ klist_iter_exit(&i);
+ } /* for closed */
+
+ /*
+ *and propagate down...
+ */
+ list_for_each_entry(tchild, &node->childs, pnode)
+ ret_notifier |= clk_trnsc_fsm(code, tchild);
+
+ return ret_notifier;
+}
+
+static void clk_initialize(struct clk *clk)
+{
+ kobject_init(&clk->kobj, &ktype_clk);
+ kobject_set_name(&clk->kobj, "%s", clk->name);
+ kobject_get(&clk->kobj);
+
+ clk->nr_clocks = 0;
+ clk->nr_active_clocks = 0;
+ clk->nr_active_devices = 0;
+ clk->towner = NULL;
+
+ klist_init(&clk->childs, klist_get_child, klist_put_child);
+ klist_init(&clk->devices, klist_get_device, klist_put_device);
+
+}
+
+/**
+ * clk_register -
+ *
+ * registers a new clk in the system.
+ * returns zero if success
+ */
+int clk_register(struct clk *clk)
+{
+ int ret = 0;
+ if (!clk)
+ return -EFAULT;
+ pr_debug("%s\n", clk->name);
+
+ clk_initialize(clk);
+
+ /* Initialize ... */
+ __clk_init(clk);
+
+ if (clk->parent) {
+#ifdef CLK_SAFE_CODE
+ /* 1. the parent has to be registered */
+ if (!check_clk(clk->parent))
+ return -ENODEV;
+ /* 2. an always enabled child has to sit on a always
+ * enabled parent!
+ */
+ if (clk->flags & CLK_ALWAYS_ENABLED &&
+ !(clk->parent->flags & CLK_ALWAYS_ENABLED))
+ return -EFAULT;
+ /* 3. a fixed child has to sit on a fixed parent */
+ if (clk_is_readonly(clk) && !clk_is_readonly(clk->parent))
+ return -EFAULT;
+#endif
+ klist_add_tail(&clk->child_node, &clk->parent->childs);
+ clk->parent->nr_clocks++;
+ }
+
+ ret = kobject_add(&clk->kobj,
+ (clk->parent ? &clk->parent->kobj : clk_kobj), clk->name);
+ if (ret)
+ goto err_0;
+
+ clk->kdevices = kobject_create_and_add("devices", &clk->kobj);
+ if (!clk->kdevices)
+ goto err_1;
+
+ klist_add_tail(&clk->node, &clk_list);
+ if (clk->flags & CLK_ALWAYS_ENABLED) {
+ __clk_enable(clk);
+ clk_notify_child_event(CHILD_CLOCK_ENABLED, clk->parent);
+ }
+ return ret;
+
+err_1:
+ /* subsystem_remove_file... removed in the common code... ??? */
+ kobject_del(&clk->kobj);
+err_0:
+ return ret;
+}
+EXPORT_SYMBOL(clk_register);
+
+/**
+ * clk_unregister -
+ * unregisters the clock from system
+ */
+int clk_unregister(struct clk *clk)
+{
+ pr_debug("\n");
+
+ if (!clk)
+ return -EFAULT;
+
+ if (!list_empty(&clk->devices.k_list))
+ return -EFAULT; /* somebody is still using this clock */
+
+ kobject_del(clk->kdevices);
+ kfree(clk->kdevices);
+ /* subsystem_remove_file... removed in the common code... ??? */
+ kobject_del(&clk->kobj);
+ klist_del(&clk->node);
+ if (clk->parent) {
+ klist_del(&clk->child_node);
+ clk->parent->nr_clocks--;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL(clk_unregister);
+
+static int clk_add_devinfo(struct pdev_clk_info *info)
+{
+ int ret = 0;
+ pr_debug("\n");
+
+#ifdef CLK_SAFE_CODE
+ if (!info || !info->clk || !check_clk(info->clk))
+ return -EFAULT;
+#endif
+ ret = sysfs_create_link(info->clk->kdevices, &info->pdev->dev.kobj,
+ dev_name(&info->pdev->dev));
+ if (ret) {
+ pr_debug(" Error %d\n", ret);
+ return ret;
+ }
+ klist_add_tail(&info->node, &info->clk->devices);
+
+ return 0;
+}
+
+static int clk_del_devinfo(struct pdev_clk_info *info)
+{
+ pr_debug("\n");
+
+#ifdef CLK_SAFE_CODE
+ if (!info || !info->clk || !check_clk(info->clk))
+ return -EFAULT;
+#endif
+ sysfs_remove_link(info->clk->kdevices, dev_name(&info->pdev->dev));
+ klist_del(&info->node);
+
+#ifndef CONFIG_PM_RUNTIME
+ /*
+ * Without PM_RUNTIME the GCF assumes the device is
+ * 'not active' when it's removed
+ */
+ clk_notify_child_event(CHILD_DEVICE_DISABLED, info->clk);
+#endif
+ return 0;
+}
+
+int clk_probe_device(struct platform_device *dev, enum pdev_probe_state state)
+{
+ int idx;
+ switch (state) {
+ case PDEV_PROBEING:
+ /* before the .probe function is called the GCF
+ * has to turn-on _all_ the clocks the device uses
+ * to garantee a safe .probe
+ */
+ for (idx = 0; idx < pdevice_num_clocks(dev); ++idx)
+ if (pdevice_clock(dev, idx))
+ clk_enable(pdevice_clock(dev, idx));
+ return 0;
+ case PDEV_PROBED:
+#ifdef CONFIG_PM_RUNTIME
+ /*
+ * Here the GCF should check the device's pm_runtime state
+ * And if the device is suspended the clk_frmwk can turn-off the clocks
+ */
+#else
+ /*
+ * Without PM_RUNTIME the GCF assumes the device is active
+ */
+ for (idx = 0; idx < pdevice_num_clocks(dev); ++idx)
+ clk_notify_child_event(CHILD_DEVICE_ENABLED,
+ pdevice_clock(dev, idx));
+#endif
+ break;
+ case PDEV_PROBE_FAILED:
+ /*
+ * TO DO something...
+ */
+ break;
+ }
+ return 0;
+}
+
+int clk_add_device(struct platform_device *dev, enum pdev_add_state state)
+{
+ int idx;
+ int ret;
+
+ if (!dev)
+ return -EFAULT;
+
+ switch (state) {
+ case PDEV_ADDING:
+ case PDEV_ADD_FAILED:
+ /*
+ * TO DO something
+ */
+ return 0;
+ case PDEV_ADDED:
+ break;
+ }
+ /* case PDEV_ADDED ... */
+ if (!dev->clks || !pdevice_num_clocks(dev))
+ return 0; /* this device will not use
+ the clk framework */
+
+ pr_debug("%s.%d with %u clocks\n", dev->name, dev->id,
+ (unsigned int)pdevice_num_clocks(dev));
+
+ dev->clk_state = 0;
+ for (idx = 0; idx < pdevice_num_clocks(dev); ++idx) {
+ if (!pdevice_clock(dev, idx)) { /* clk can not be NULL... */
+ pr_debug("Error clock NULL\n");
+ continue;
+ }
+ pr_debug("->under %s\n", dev->clks[idx].clk->name);
+ dev->clks[idx].pdev = dev;
+ ret = clk_add_devinfo(&dev->clks[idx]);
+ if (ret)
+ goto err_0;
+ }
+
+ return 0;
+err_0:
+ for (--idx; idx >= 0; --idx)
+ clk_del_devinfo(&dev->clks[idx]);
+
+ return -EINVAL;
+}
+
+int clk_del_device(struct platform_device *dev)
+{
+ int idx;
+ if (!dev)
+ return -EFAULT;
+
+ for (idx = 0; idx < pdevice_num_clocks(dev); ++idx)
+ clk_del_devinfo(&dev->clks[idx]);
+
+ return 0;
+}
+
+void clk_put(struct clk *clk)
+{
+ if (clk && !IS_ERR(clk))
+ kobject_put(&clk->kobj);
+}
+
+static int clk_is_parent(struct clk const *child, struct clk const *parent)
+{
+ if (!child || !parent)
+ return 0;
+ if (!child->parent)
+ return 0;
+ if (child->parent == parent)
+ return 1;
+ else
+ return clk_is_parent(child->parent, parent);
+}
+
+int clk_enable(struct clk *clk)
+{
+ int ret;
+ struct clk_tnode transaction;
+ struct clk_event event;
+
+ event = EVENT(clk, 0, CLK_UNDEFINED_RATE);
+ transaction = TRANSACTION_ROOT(1, &event);
+
+ pr_debug("%s\n", clk->name);
+
+
+ if (clk->flags & CLK_ALWAYS_ENABLED || clk_is_enabled(clk))
+ return 0;
+
+ if (clk->parent) {
+ /* turn-on the parent if the parent is 'auto_switch' */
+ clk_notify_child_event(CHILD_CLOCK_ENABLED, clk->parent);
+
+ if (!clk_is_enabled(clk->parent)) {
+ /* the parent is still disabled... */
+ clk_notify_child_event(CHILD_CLOCK_DISABLED,
+ clk->parent);
+ return -EINVAL;
+ }
+ }
+
+ ret = clk_trnsc_fsm(TRNSC_ENTER_CLOCK, &transaction);
+ if (ret) {
+ ret = -EPERM;
+ goto err_0;
+ }
+
+ /* if not zero somebody doens't agree the clock update */
+ ret = clk_trnsc_fsm(TRNSC_ENTER_DEVICE, &transaction);
+ if (ret) {
+ ret = -EPERM;
+ goto err_1;
+ }
+
+ clk_trnsc_fsm(TRNSC_PRE_DEVICE, &transaction);
+
+ ret = __clk_enable(clk);
+
+ event.new_rate = clk_get_rate(clk);
+
+ clk_trnsc_fsm(TRNSC_CHANGE_CLOCK, &transaction);
+
+ clk_trnsc_fsm(TRNSC_POST_DEVICE, &transaction);
+
+err_1:
+ clk_trnsc_fsm(TRNSC_EXIT_DEVICE, &transaction);
+
+err_0:
+ clk_trnsc_fsm(TRNSC_EXIT_CLOCK, &transaction);
+
+ if (ret)
+ clk_notify_child_event(CHILD_CLOCK_DISABLED, clk->parent);
+
+ return ret;
+}
+EXPORT_SYMBOL(clk_enable);
+
+/**
+ * clk_disable -
+ * disables the clock
+ * Is isn't really good that it's a 'void' function...
+ * but this is common interface
+ */
+void clk_disable(struct clk *clk)
+{
+ struct clk_tnode transaction;
+ struct clk_event event;
+ int ret;
+
+ event = EVENT(clk, clk_get_rate(clk), 0);
+ transaction = TRANSACTION_ROOT(1, &event);
+
+ pr_debug("\n");
+
+ if (clk->flags & CLK_ALWAYS_ENABLED || !clk_is_enabled(clk))
+ return;
+
+ ret = clk_trnsc_fsm(TRNSC_ENTER_CLOCK, &transaction);
+ if (ret)
+ goto err_0;
+
+ /* if not zero somebody doens't agree the clock update */
+ ret = clk_trnsc_fsm(TRNSC_ENTER_DEVICE, &transaction);
+ if (ret)
+ goto err_1;
+
+ clk_trnsc_fsm(TRNSC_PRE_DEVICE, &transaction);
+
+ __clk_disable(clk);
+
+ clk_trnsc_fsm(TRNSC_CHANGE_CLOCK, &transaction);
+
+ clk_trnsc_fsm(TRNSC_POST_DEVICE, &transaction);
+
+err_0:
+ clk_trnsc_fsm(TRNSC_EXIT_DEVICE, &transaction);
+err_1:
+ clk_trnsc_fsm(TRNSC_EXIT_CLOCK, &transaction);
+
+ clk_notify_child_event(CHILD_CLOCK_DISABLED, clk->parent);
+
+ return ;
+}
+EXPORT_SYMBOL(clk_disable);
+
+unsigned long clk_get_rate(struct clk *clk)
+{
+ return clk->rate;
+}
+EXPORT_SYMBOL(clk_get_rate);
+
+struct clk *clk_get_parent(struct clk *clk)
+{
+ return clk->parent;
+}
+EXPORT_SYMBOL(clk_get_parent);
+
+int clk_set_parent(struct clk *clk, struct clk *parent)
+{
+ int ret = -EOPNOTSUPP;
+ struct clk *old_parent = clk->parent;
+ struct clk_event event;
+ struct clk_tnode transaction;
+ int clk_was_enabled = clk_is_enabled(clk);
+
+ event = EVENT(clk, clk_get_rate(clk), CLK_UNDEFINED_RATE);
+ transaction = TRANSACTION_ROOT(1, &event);
+
+ if (!clk || !parent)
+ return -EINVAL;
+
+ if (clk->parent == parent)
+ return 0;
+
+ pr_debug("\n");
+
+ if (clk_was_enabled && !clk_is_enabled(parent))
+ /* turn-on parent if possible */
+ clk_notify_child_event(CHILD_CLOCK_ENABLED, parent);
+
+ ret = clk_trnsc_fsm(TRNSC_ENTER_CLOCK, &transaction);
+ if (ret) {
+ ret = -EPERM;
+ goto err_0;
+ }
+
+ /* if not zero somebody doens't agree the clock updated */
+ ret = clk_trnsc_fsm(TRNSC_ENTER_DEVICE, &transaction);
+ if (ret) {
+ ret = -EPERM;
+ goto err_1;
+ }
+
+ clk_trnsc_fsm(TRNSC_PRE_DEVICE, &transaction);
+
+ /* Now we updated the hw */
+ ret = __clk_set_parent(clk, parent);
+ if (ret) {
+ /* there was a problem...
+ * therefore clk is still on the old parent
+ */
+ clk->parent = old_parent; /* to be safe ! */
+ goto err_2;
+ }
+
+ klist_del(&clk->child_node);
+
+ clk->parent = parent;
+
+ ret = kobject_move(&clk->kobj, &clk->parent->kobj);
+ if (ret)
+ ;
+
+ klist_add_tail(&clk->child_node, &clk->parent->childs);
+
+ clk->parent->nr_clocks++;
+ old_parent->nr_clocks--;
+
+err_2:
+ event.new_rate = clk_get_rate(clk);
+
+ clk_trnsc_fsm(TRNSC_CHANGE_CLOCK, &transaction);
+
+ clk_trnsc_fsm(TRNSC_POST_DEVICE, &transaction);
+
+err_1:
+ clk_trnsc_fsm(TRNSC_EXIT_DEVICE, &transaction);
+err_0:
+ clk_trnsc_fsm(TRNSC_EXIT_CLOCK, &transaction);
+
+ if (clk_was_enabled && !ret) {
+ /* 5. to decrease the old_parent nchild counter */
+ clk_notify_child_event(CHILD_CLOCK_DISABLED, old_parent);
+ /* 5. increase the new_parent nchild counter */
+ clk_notify_child_event(CHILD_CLOCK_ENABLED, clk->parent);
+ /* 6. to decrease the old_parent nchild counter */
+ clk_notify_child_event(CHILD_CLOCK_DISABLED, old_parent);
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL(clk_set_parent);
+
+int clk_set_rate(struct clk *clk, unsigned long rate)
+{
+ int ret = -EOPNOTSUPP;
+ struct clk_event event;
+ struct clk_tnode transaction;
+
+ event = EVENT(clk, clk_get_rate(clk), clk_round_rate(clk, rate));
+ transaction = TRANSACTION_ROOT(1, &event);
+
+ pr_debug("\n");
+
+ if (clk_is_readonly(clk))
+ /* read only clock doesn't have to be "touched" !!!! */
+ return -EPERM;
+
+ if (event.new_rate == clk_get_rate(clk))
+ return 0;
+
+ ret = clk_trnsc_fsm(TRNSC_ENTER_CLOCK, &transaction);
+ if (ret) {
+ ret = -EPERM;
+ goto err_0;
+ }
+
+ /* if not zero somebody doens't agree the clock updated */
+ ret = clk_trnsc_fsm(TRNSC_ENTER_DEVICE, &transaction);
+ if (ret) {
+ ret = -EPERM;
+ goto err_1;
+ }
+
+ clk_trnsc_fsm(TRNSC_PRE_DEVICE, &transaction);
+
+ __clk_set_rate(clk, event.new_rate);
+ /* reload new_rate to avoid hw rounding... */
+ event.new_rate = clk_get_rate(clk);
+
+ clk_trnsc_fsm(TRNSC_CHANGE_CLOCK, &transaction);
+ clk_trnsc_fsm(TRNSC_POST_DEVICE, &transaction);
+
+err_1:
+ clk_trnsc_fsm(TRNSC_EXIT_DEVICE, &transaction);
+err_0:
+ clk_trnsc_fsm(TRNSC_EXIT_CLOCK, &transaction);
+
+ return ret;
+}
+EXPORT_SYMBOL(clk_set_rate);
+
+long clk_round_rate(struct clk *clk, unsigned long rate)
+{
+ pr_debug("\n");
+
+ if (likely(clk->ops && clk->ops->round))
+ return clk->ops->round(clk, rate);
+ return rate;
+}
+EXPORT_SYMBOL(clk_round_rate);
+
+unsigned long clk_evaluate_rate(struct clk *clk, unsigned long prate)
+{
+ pr_debug("\n");
+ if (!clk->parent)/* without parent this function has no meaning */
+ return CLK_UNDEFINED_RATE;
+
+ if (!prate)/* on parent disabled than disable the child */
+ return 0;
+
+ if (likely(clk->ops && clk->ops->eval))
+ return clk->ops->eval(clk, prate);
+
+ return CLK_UNDEFINED_RATE;
+}
+EXPORT_SYMBOL(clk_evaluate_rate);
+
+int clk_set_rates(struct clk **clks, unsigned long *rates, unsigned long nclks)
+{
+ int i, ret = 0;
+ struct clk_event *evt;
+ struct clk_tnode transaction = TRANSACTION_ROOT(nclks, NULL)
+
+ pr_debug("\n");
+
+ if (!clks || !rates || !nclks)
+ return -EINVAL;
+ evt = kmalloc(sizeof(*evt) *
+ tnode_get_size(&transaction), GFP_KERNEL);
+
+ if (!evt)
+ return -ENOMEM;
+
+ tnode_set_events(&transaction, evt);
+
+ for (i = 0; i < tnode_get_size(&transaction); ++i) {
+ tnode_set_clock(&transaction, i, clks[i]);
+ tnode_get_event(&transaction, i)->old_rate =
+ clk_get_rate(clks[i]);
+ tnode_get_event(&transaction, i)->new_rate =
+ clk_round_rate(clks[i], rates[i]);
+ }
+
+ ret = clk_trnsc_fsm(TRNSC_ENTER_CLOCK, &transaction);
+ if (ret) {
+ ret = -EPERM;
+ goto err_0;
+ }
+
+ /* if not zero somebody doens't agree the clock updated */
+ ret = clk_trnsc_fsm(TRNSC_ENTER_DEVICE, &transaction);
+ if (ret) {
+ ret = -EPERM;
+ goto err_1;
+ }
+
+ clk_trnsc_fsm(TRNSC_PRE_DEVICE, &transaction);
+
+ for (i = 0; i < tnode_get_size(&transaction); ++i) {
+ if (!clk_is_enabled(clks[i]) && rates[i])
+ ret |= __clk_enable(clks[i]);
+ else if (clk_is_enabled(clks[i]) && !rates[i])
+ ret |= __clk_disable(clks[i]);
+ else
+ ret |= __clk_set_rate(clks[i], rates[i]);
+
+ /* reload new_rate to avoid hw rounding... */
+ tnode_get_event(&transaction, i)->new_rate =
+ clk_get_rate(clks[i]);
+ }
+
+ clk_trnsc_fsm(TRNSC_CHANGE_CLOCK, &transaction);
+
+ clk_trnsc_fsm(TRNSC_POST_DEVICE, &transaction);
+
+err_1:
+ clk_trnsc_fsm(TRNSC_EXIT_DEVICE, &transaction);
+
+err_0:
+ clk_trnsc_fsm(TRNSC_EXIT_CLOCK, &transaction);
+
+ kfree(evt);
+ return ret;
+}
+EXPORT_SYMBOL(clk_set_rates);
+
+struct clk *clk_get(struct device *dev, const char *id)
+{
+ struct clk *clk = NULL;
+ struct clk *clkp;
+ struct klist_iter i;
+ int found = 0, idno;
+
+ mutex_lock(&clk_list_sem);
+#if 0
+ if (dev == NULL || dev->bus != &platform_bus_type)
+ idno = -1;
+ else
+ idno = to_platform_device(dev)->id;
+
+ klist_iter_init(&clk_list, &i);
+ while ((clkp = next_clock(&i)) && !found)
+ if (clk->id == idno && strcmp(id, clk->name) == 0 &&
+ try_module_get(clk->owner)) {
+ clk = clkp;
+ found = 1;
+ }
+ klist_iter_exit(&i);
+
+ if (found)
+ goto _found;
+#endif
+ klist_iter_init(&clk_list, &i);
+ while ((clkp = next_clock(&i)))
+ if (strcmp(id, clkp->name) == 0
+ && try_module_get(clkp->owner)) {
+ clk = clkp;
+ break;
+ }
+ klist_iter_exit(&i);
+_found:
+ mutex_unlock(&clk_list_sem);
+ return clk;
+}
+EXPORT_SYMBOL(clk_get);
+
+int clk_for_each(int (*fn) (struct clk *clk, void *data), void *data)
+{
+ struct clk *clkp;
+ struct klist_iter i;
+ int result = 0;
+
+ if (!fn)
+ return -EFAULT;
+
+ pr_debug("\n");
+ mutex_lock(&clk_list_sem);
+ klist_iter_init(&clk_list, &i);
+
+ while ((clkp = next_clock(&i)))
+ result |= fn(clkp, data);
+
+ klist_iter_exit(&i);
+ mutex_unlock(&clk_list_sem);
+ return result;
+}
+EXPORT_SYMBOL(clk_for_each);
+
+int clk_for_each_child(struct clk *clk,
+ int (*fn) (struct clk *clk, void *data), void *data)
+{
+ struct clk *clkp;
+ struct klist_iter i;
+ int result = 0;
+
+ if (!clk || !fn)
+ return -EFAULT;
+
+ klist_iter_init(&clk->childs, &i);
+
+ while ((clkp = next_child_clock(&i)))
+ result |= fn(clkp, data);
+
+ klist_iter_exit(&i);
+
+ return result;
+}
+EXPORT_SYMBOL(clk_for_each_child);
+
+static int __init early_clk_complete(struct clk *clk, void *data)
+{
+ int ret;
+
+ ret = kobject_add(&clk->kobj,
+ (clk->parent ? &clk->parent->kobj : clk_kobj),
+ clk->name);
+ if (ret)
+ return ret;
+
+ clk->kdevices = kobject_create_and_add("devices", &clk->kobj);
+ if (!clk->kdevices)
+ return -EINVAL;
+
+ return 0;
+}
+
+int __init early_clk_register(struct clk *clk)
+{
+ int retval = 0;
+ if (!clk)
+ return -EFAULT;
+ pr_debug("%s\n", clk->name);
+
+ clk_initialize(clk);
+
+ /* Initialize ... */
+ __clk_init(clk);
+
+ if (clk->parent) {
+#ifdef CLK_SAFE_CODE
+ /* 1. the parent has to be registered */
+ if (!check_clk(clk->parent))
+ return -ENODEV;
+ /* 2. an always enabled child has to sit on a always
+ * enabled parent!
+ */
+ if (clk->flags & CLK_ALWAYS_ENABLED &&
+ !(clk->parent->flags & CLK_ALWAYS_ENABLED))
+ return -EFAULT;
+ /* 3. a fixed child has to sit on a fixed parent */
+ if (clk_is_readonly(clk) && !clk_is_readonly(clk->parent))
+ return -EFAULT;
+#endif
+ klist_add_tail(&clk->child_node, &clk->parent->childs);
+ clk->parent->nr_clocks++;
+ }
+
+ klist_add_tail(&clk->node, &clk_list);
+ if (clk->flags & CLK_ALWAYS_ENABLED) {
+ __clk_enable(clk);
+ clk_notify_child_event(CHILD_CLOCK_ENABLED, clk->parent);
+ }
+ return retval;
+}
+
+int __init clock_init(void)
+{
+ clk_kobj = kobject_create_and_add("clocks", NULL);
+ if (!clk_kobj)
+ return -EINVAL ;
+
+ clk_for_each(early_clk_complete, NULL);
+
+ printk(KERN_INFO CLK_NAME " " CLK_VERSION "\n");
+
+ return 0;
+}
+
diff --git a/drivers/base/clk.h b/drivers/base/clk.h
new file mode 100644
index 0000000..61672ef
--- /dev/null
+++ b/drivers/base/clk.h
@@ -0,0 +1,319 @@
+/*
+ -------------------------------------------------------------------------
+ clk.h
+ -------------------------------------------------------------------------
+ (C) STMicroelectronics 2008
+ (C) STMicroelectronics 2009
+ Author: Francesco M. Virlinzi <francesco.virlinzi at st.com>
+ ----------------------------------------------------------------------------
+ May be copied or modified under the terms of the GNU General Public
+ License v.2 ONLY. See linux/COPYING for more information.
+
+ ------------------------------------------------------------------------- */
+
+#ifdef CONFIG_GENERIC_CLK_FM
+
+#include <linux/clk.h>
+#include <linux/platform_device.h>
+#include <linux/kobject.h>
+#include <linux/klist.h>
+#include <linux/list.h>
+#include <linux/notifier.h>
+#include <asm/atomic.h>
+
+enum clk_ops_id {
+ __CLK_INIT = 0,
+ __CLK_ENABLE,
+ __CLK_DISABLE,
+ __CLK_SET_RATE,
+ __CLK_SET_PARENT,
+ __CLK_RECALC,
+ __CLK_ROUND,
+ __CLK_EVAL,
+};
+
+extern struct klist clk_list;
+/**
+ * clk_tnode
+ * it's the internal strucure used to track each node
+ * in the transaction graph.
+ * _NO_ api is showed to the other modules
+ */
+struct clk_tnode {
+ /** @tid: the tnode id */
+ unsigned long tid;
+ /** @size: how may clock are involved in this tnode */
+ unsigned long size;
+ /** @parent: the parent tnode */
+ struct clk_tnode *parent;
+ /* @events_map: a bitmap to declare the
+ * valid events in this tnode
+ */
+ unsigned long events_map;
+ /** @events: the event array of this tnode */
+ struct clk_event *events;
+ /** @child: links the childres tnode */
+ struct list_head childs;
+ /** @pnode: links the tnode to the parent */
+ struct list_head pnode;
+};
+
+/*
+ * tnode_get_size -
+ * returns the number of events in the transaction
+ */
+static inline unsigned long
+tnode_get_size(struct clk_tnode *tnode)
+{
+ return tnode->size;
+}
+
+static inline unsigned long
+tnode_get_map(struct clk_tnode *tnode)
+{
+ return tnode->events_map;
+}
+
+static inline unsigned long
+tnode_check_map_id(struct clk_tnode *node, int id)
+{
+ return node->events_map & (1 << id);
+}
+
+static inline void
+tnode_set_map_id(struct clk_tnode *node, int id)
+{
+ node->events_map |= (1 << id);
+}
+
+static inline unsigned long
+tnode_get_id(struct clk_tnode *node)
+{
+ return node->tid;
+}
+
+static inline struct clk_event*
+tnode_get_event(struct clk_tnode *node, int id)
+{
+ return &(node->events[id]);
+}
+
+static inline struct clk_event *tnode_get_events(struct clk_tnode *node)
+{
+ return tnode_get_event(node, 0);
+}
+
+static inline void
+tnode_set_events(struct clk_tnode *node, struct clk_event *events)
+{
+ node->events = events;
+}
+
+static inline struct clk*
+tnode_get_clock(struct clk_tnode *node, int id)
+{
+ return tnode_get_event(node, id)->clk;
+}
+
+static inline void
+tnode_set_clock(struct clk_tnode *node, int id, struct clk *clk)
+{
+ node->events[id].clk = clk;
+}
+
+static inline struct clk_tnode *tnode_get_parent(struct clk_tnode *node)
+{
+ return node->parent;
+}
+
+#define tnode_for_each_valid_events(node, _j) \
+ for ((_j) = (ffs(tnode_get_map(node)) - 1); \
+ (_j) < tnode_get_size((node)); ++(_j)) \
+ if (tnode_check_map_id((node), (_j)))
+
+#define EVENT(_clk, _oldrate, _newrate) \
+ (struct clk_event) \
+ { \
+ .clk = (struct clk *)(_clk), \
+ .old_rate = (unsigned long)(_oldrate), \
+ .new_rate = (unsigned long)(_newrate), \
+ };
+
+#define TRANSACTION_ROOT(_num, _event) \
+ (struct clk_tnode) { \
+ .tid = atomic_inc_return(&transaction_counter), \
+ .size = (_num), \
+ .events = (struct clk_event *)(_event), \
+ .parent = NULL, \
+ .childs = LIST_HEAD_INIT(transaction.childs), \
+ .events_map = 0, \
+ };
+
+#define klist_function_support(_name, _type, _field, _kobj) \
+static void klist_get_##_name(struct klist_node *n) \
+{ \
+ struct _type *entry = container_of(n, struct _type, _field); \
+ kobject_get(&entry->_kobj); \
+} \
+static void klist_put_##_name(struct klist_node *n) \
+{ \
+ struct _type *entry = container_of(n, struct _type, _field); \
+ kobject_put(&entry->_kobj); \
+}
+
+#define klist_entry_support(name, type, field) \
+static struct type *next_##name(struct klist_iter *i) \
+{ struct klist_node *n = klist_next(i); \
+ return n ? container_of(n, struct type, field) : NULL; \
+}
+
+static inline void
+clk_event_init(struct clk_event *evt, struct clk *clk,
+ unsigned long oldrate, unsigned long newrate)
+{
+ evt->clk = clk;
+ evt->old_rate = oldrate;
+ evt->new_rate = newrate;
+}
+
+enum clk_fsm_e {
+ TRNSC_ENTER_CLOCK = 0x10,
+ TRNSC_ENTER_DEVICE = NOTIFY_CLK_ENTERCHANGE, /* 0x1 */
+ TRNSC_PRE_DEVICE = NOTIFY_CLK_PRECHANGE, /* 0x2 */
+ TRNSC_CHANGE_CLOCK = 0x20,
+ TRNSC_POST_DEVICE = NOTIFY_CLK_POSTCHANGE, /* 0x4 */
+ TRNSC_EXIT_DEVICE = NOTIFY_CLK_EXITCHANGE, /* 0x8 */
+ TRNSC_EXIT_CLOCK = 0x40
+};
+
+#define DEV_SUSPENDED_ON_TRANSACTION (0x10)
+#define DEV_RESUMED_ON_TRANSACTION (0x20)
+#define DEV_ON_TRANSACTION (TRNSC_ENTER_DEVICE | \
+ TRNSC_PRE_DEVICE | \
+ TRNSC_POST_DEVICE | \
+ TRNSC_EXIT_DEVICE)
+
+static inline int
+pdev_transaction_move_on(struct platform_device *dev, unsigned int value)
+{
+ int ret = -EINVAL;
+ unsigned long flag;
+#ifdef CONFIG_CLK_DEBUG
+ static const char *dev_state[] = {
+ "dev_enter",
+ "dev_pre",
+ "dev_post",
+ "dev_exit"
+ };
+
+ unsigned long old = dev->clk_state & DEV_ON_TRANSACTION;
+ int was = 0, is = 0;
+ if (
+ (old == 0 && value == TRNSC_ENTER_DEVICE) ||
+ (old == TRNSC_ENTER_DEVICE && value == TRNSC_EXIT_DEVICE) ||
+ (old == TRNSC_ENTER_DEVICE && value == TRNSC_PRE_DEVICE) ||
+ (old == TRNSC_PRE_DEVICE && value == TRNSC_POST_DEVICE) ||
+ (old == TRNSC_POST_DEVICE && value == TRNSC_EXIT_DEVICE))
+ goto ok;
+ switch (old) {
+ case TRNSC_ENTER_DEVICE:
+ was = 0;
+ break;
+ case TRNSC_PRE_DEVICE:
+ was = 1;
+ break;
+ case TRNSC_POST_DEVICE:
+ was = 2;
+ break;
+ case TRNSC_EXIT_DEVICE:
+ was = 3;
+ break;
+ }
+ switch (value) {
+ case TRNSC_ENTER_DEVICE:
+ is = 0;
+ break;
+ case TRNSC_PRE_DEVICE:
+ is = 1;
+ break;
+ case TRNSC_POST_DEVICE:
+ is = 2;
+ break;
+ case TRNSC_EXIT_DEVICE:
+ is = 3;
+ break;
+ }
+ printk(KERN_ERR "The device %s.%d shows a wrong evolution during "
+ "a clock transaction\nDev state was %s and moved on %s\n",
+ dev->name, dev->id, dev_state[was], dev_state[is]);
+ok:
+#endif
+ local_irq_save(flag);
+ if ((dev->clk_state & DEV_ON_TRANSACTION) != value) {
+ dev->clk_state &= ~DEV_ON_TRANSACTION;
+ dev->clk_state |= value;
+ ret = 0;
+ }
+ local_irq_restore(flag);
+ return ret;
+}
+
+static inline int
+clk_set_towner(struct clk *clk, struct clk_tnode *node)
+{
+ return atomic_cmpxchg((atomic_t *)&clk->towner, 0, (int)node);
+}
+
+static inline void
+clk_clean_towner(struct clk *clk)
+{
+ atomic_set((atomic_t *)(&clk->towner), 0);
+}
+
+static inline int
+clk_is_enabled(struct clk *clk)
+{
+ return clk->rate != 0;
+}
+
+static inline int
+clk_is_readonly(struct clk *clk)
+{
+ return !clk->ops || !clk->ops->set_rate;
+}
+
+static inline int
+clk_allow_propagation(struct clk *clk)
+{
+ return !!(clk->flags & CLK_EVENT_PROPAGATES);
+}
+
+static inline int
+clk_is_auto_switching(struct clk *clk)
+{
+ return !!(clk->flags & CLK_AUTO_SWITCHING);
+}
+
+static inline int
+clk_follow_parent(struct clk *clk)
+{
+ return !!(clk->flags & CLK_FOLLOW_PARENT);
+}
+
+enum pdev_add_state {
+ PDEV_ADDING,
+ PDEV_ADDED,
+ PDEV_ADD_FAILED,
+};
+
+enum pdev_probe_state {
+ PDEV_PROBEING,
+ PDEV_PROBED,
+ PDEV_PROBE_FAILED,
+};
+
+int clk_add_device(struct platform_device *dev, enum pdev_add_state state);
+int clk_probe_device(struct platform_device *dev, enum pdev_probe_state state);
+int clk_del_device(struct platform_device *dev);
+
+#endif
diff --git a/drivers/base/clk_pm.c b/drivers/base/clk_pm.c
new file mode 100644
index 0000000..56c1760
--- /dev/null
+++ b/drivers/base/clk_pm.c
@@ -0,0 +1,197 @@
+/*
+ * -------------------------------------------------------------------------
+ * clk_pm.c
+ * -------------------------------------------------------------------------
+ * (C) STMicroelectronics 2008
+ * (C) STMicroelectronics 2009
+ * Author: Francesco M. Virlinzi <francesco.virlinzi at st.com>
+ * -------------------------------------------------------------------------
+ * May be copied or modified under the terms of the GNU General Public
+ * License v.2 ONLY. See linux/COPYING for more information.
+ *
+ * -------------------------------------------------------------------------
+ */
+
+#include <linux/clk.h>
+#include <linux/klist.h>
+#include <linux/list.h>
+#include <linux/sysdev.h>
+#include <linux/device.h>
+#include <linux/kref.h>
+#include <linux/kobject.h>
+#include <linux/err.h>
+#include <linux/spinlock.h>
+#include <linux/proc_fs.h>
+#include "power/power.h"
+#include "clk.h"
+#include "base.h"
+
+static int
+__clk_operations(struct clk *clk, unsigned long rate, enum clk_ops_id id_ops)
+{
+ int ret = -EINVAL;
+ unsigned long *ops_fns = (unsigned long *)clk->ops;
+ if (likely(ops_fns && ops_fns[id_ops])) {
+ int (*fns)(struct clk *clk, unsigned long rate)
+ = (void *)ops_fns[id_ops];
+ unsigned long flags;
+ spin_lock_irqsave(&clk->lock, flags);
+ ret = fns(clk, rate);
+ spin_unlock_irqrestore(&clk->lock, flags);
+ }
+ return ret;
+}
+
+static inline int __clk_init(struct clk *clk)
+{
+ return __clk_operations(clk, 0, __CLK_INIT);
+}
+
+static inline int __clk_enable(struct clk *clk)
+{
+ return __clk_operations(clk, 0, __CLK_ENABLE);
+}
+
+static inline int __clk_disable(struct clk *clk)
+{
+ return __clk_operations(clk, 0, __CLK_DISABLE);
+}
+
+static inline int __clk_set_rate(struct clk *clk, unsigned long rate)
+{
+ return __clk_operations(clk, rate, __CLK_SET_RATE);
+}
+
+static inline int __clk_set_parent(struct clk *clk, struct clk *parent)
+{
+ return __clk_operations(clk, (unsigned long)parent, __CLK_SET_PARENT);
+}
+
+static inline int __clk_recalc_rate(struct clk *clk)
+{
+ return __clk_operations(clk, 0, __CLK_RECALC);
+}
+
+static inline int pm_clk_ratio(struct clk *clk)
+{
+ register unsigned int val, exp;
+
+ val = ((clk->flags >> CLK_PM_RATIO_SHIFT) &
+ ((1 << CLK_PM_RATIO_NRBITS) - 1)) + 1;
+ exp = ((clk->flags >> CLK_PM_EXP_SHIFT) &
+ ((1 << CLK_PM_EXP_NRBITS) - 1));
+
+ return val << exp;
+}
+
+static inline int pm_clk_is_off(struct clk *clk)
+{
+ return ((clk->flags & CLK_PM_TURNOFF) == CLK_PM_TURNOFF);
+}
+
+static inline void pm_clk_set(struct clk *clk, int edited)
+{
+#define CLK_PM_EDITED (1 << CLK_PM_EDIT_SHIFT)
+ clk->flags &= ~CLK_PM_EDITED;
+ clk->flags |= (edited ? CLK_PM_EDITED : 0);
+}
+
+static inline int pm_clk_is_modified(struct clk *clk)
+{
+ return ((clk->flags & CLK_PM_EDITED) != 0);
+}
+
+static int clk_resume_from_standby(struct clk *clk, void *data)
+{
+ pr_debug("\n");
+ if (!likely(clk->ops))
+ return 0;
+ /* check if the pm modified the clock */
+ if (!pm_clk_is_modified(clk))
+ return 0;;
+ pm_clk_set(clk, 0);
+ if (pm_clk_is_off(clk))
+ __clk_enable(clk);
+ else
+ __clk_set_rate(clk, clk->rate * pm_clk_ratio(clk));
+ return 0;
+}
+
+static int clk_on_standby(struct clk *clk, void *data)
+{
+ pr_debug("\n");
+
+ if (!clk->ops)
+ return 0;
+ if (!clk->rate) /* already disabled */
+ return 0;
+
+ pm_clk_set(clk, 1); /* set as modified */
+ if (pm_clk_is_off(clk)) /* turn-off */
+ __clk_disable(clk);
+ else /* reduce */
+ __clk_set_rate(clk, clk->rate / pm_clk_ratio(clk));
+ return 0;
+}
+
+static int clk_resume_from_hibernation(struct clk *clk, void *data)
+{
+ unsigned long rate = clk->rate;
+ pr_debug("\n");
+ __clk_set_parent(clk, clk->parent);
+ __clk_set_rate(clk, rate);
+ __clk_recalc_rate(clk);
+ return 0;
+}
+
+static int clks_sysdev_suspend(struct sys_device *dev, pm_message_t state)
+{
+ static pm_message_t prev_state;
+
+ switch (state.event) {
+ case PM_EVENT_ON:
+ switch (prev_state.event) {
+ case PM_EVENT_FREEZE: /* Resumeing from hibernation */
+ clk_for_each(clk_resume_from_hibernation, NULL);
+ break;
+ case PM_EVENT_SUSPEND:
+ clk_for_each(clk_resume_from_standby, NULL);
+ break;
+ }
+ case PM_EVENT_SUSPEND:
+ clk_for_each(clk_on_standby, NULL);
+ break;
+ case PM_EVENT_FREEZE:
+ break;
+ }
+ prev_state = state;
+ return 0;
+}
+
+static int clks_sysdev_resume(struct sys_device *dev)
+{
+ return clks_sysdev_suspend(dev, PMSG_ON);
+}
+
+static struct sysdev_class clk_sysdev_class = {
+ .name = "clks",
+};
+
+static struct sysdev_driver clks_sysdev_driver = {
+ .suspend = clks_sysdev_suspend,
+ .resume = clks_sysdev_resume,
+};
+
+static struct sys_device clks_sysdev_dev = {
+ .cls = &clk_sysdev_class,
+};
+
+static int __init clk_sysdev_init(void)
+{
+ sysdev_class_register(&clk_sysdev_class);
+ sysdev_driver_register(&clk_sysdev_class, &clks_sysdev_driver);
+ sysdev_register(&clks_sysdev_dev);
+ return 0;
+}
+
+subsys_initcall(clk_sysdev_init);
diff --git a/drivers/base/clk_utils.c b/drivers/base/clk_utils.c
new file mode 100644
index 0000000..a222aa7
--- /dev/null
+++ b/drivers/base/clk_utils.c
@@ -0,0 +1,456 @@
+/*
+ * -------------------------------------------------------------------------
+ * clk_utils.c
+ * -------------------------------------------------------------------------
+ * (C) STMicroelectronics 2008
+ * (C) STMicroelectronics 2009
+ * Author: Francesco M. Virlinzi <francesco.virlinzi at st.com>
+ * -------------------------------------------------------------------------
+ * May be copied or modified under the terms of the GNU General Public
+ * License v.2 ONLY. See linux/COPYING for more information.
+ *
+ * -------------------------------------------------------------------------
+ */
+
+#include <linux/platform_device.h>
+#include <linux/clk.h>
+#include <linux/klist.h>
+#include <linux/list.h>
+#include <linux/delay.h>
+#include <linux/sysdev.h>
+#include <linux/kref.h>
+#include <linux/kobject.h>
+#include <linux/err.h>
+#include <linux/spinlock.h>
+#include <asm/atomic.h>
+#include "power/power.h"
+#include "clk.h"
+#include "base.h"
+
+int clk_generic_notify(unsigned long code,
+ struct platform_device *pdev, void *data)
+{
+ struct clk_event *event = (struct clk_event *)data;
+ unsigned long event_decode = clk_event_decode(event);
+
+ switch (code) {
+ case NOTIFY_CLK_ENTERCHANGE:
+ return NOTIFY_EVENT_HANDLED; /* to accept */
+
+ case NOTIFY_CLK_PRECHANGE:
+ /* without clock (not still enabled) the device can not work */
+ if (event_decode == _CLK_ENABLE)
+ return NOTIFY_EVENT_NOTHANDLED;
+ return NOTIFY_EVENT_HANDLED; /* to suspend */
+
+ case NOTIFY_CLK_POSTCHANGE:
+ /* without clock (just disabled) the device can not work */
+ if (event_decode == _CLK_DISABLE)
+ return NOTIFY_EVENT_NOTHANDLED;
+ return NOTIFY_EVENT_HANDLED; /* to resume */
+
+ case NOTIFY_CLK_EXITCHANGE:
+ return NOTIFY_EVENT_HANDLED;
+ }
+
+ return NOTIFY_EVENT_HANDLED;
+}
+EXPORT_SYMBOL(clk_generic_notify);
+
+unsigned long clk_generic_evaluate_rate(struct clk *clk, unsigned long prate)
+{
+ unsigned long current_prate;
+
+ if (!clk->parent)
+ return -EINVAL;
+
+ if (!prate) /* if zero return zero (on disable: disable!) */
+ return 0;
+
+ if (prate == CLK_UNDEFINED_RATE) /* on undefined: undefined */
+ return CLK_UNDEFINED_RATE;
+
+ current_prate = clk_get_rate(clk->parent);
+ if (current_prate == prate)
+ return clk_get_rate(clk);
+
+ if (current_prate > prate) /* down scale */
+ return (clk_get_rate(clk) * prate) / current_prate;
+ else
+ return (clk_get_rate(clk) / current_prate) * prate;
+}
+EXPORT_SYMBOL(clk_generic_evaluate_rate);
+
+#ifdef CONFIG_PROC_FS
+/*
+ * The "clocks" file is created under /proc
+ * to list all the clocks registered in the system
+ */
+#include <linux/proc_fs.h>
+#include <linux/seq_file.h>
+static void *clk_seq_next(struct seq_file *s, void *v, loff_t *pos)
+{
+ struct list_head *tmp;
+ union {
+ loff_t value;
+ long parts[2];
+ } ltmp;
+
+ ltmp.value = *pos;
+ tmp = (struct list_head *)ltmp.parts[0];
+ tmp = tmp->next;
+ ltmp.parts[0] = (long)tmp;
+
+ *pos = ltmp.value;
+
+ if (tmp == &clk_list.k_list)
+ return NULL; /* No more to read */
+
+ return pos;
+}
+
+static void *clk_seq_start(struct seq_file *s, loff_t *pos)
+{
+ if (!*pos) { /* first call! */
+ union {
+ loff_t value;
+ long parts[2];
+ } ltmp;
+ ltmp.parts[0] = (long) clk_list.k_list.next;
+ *pos = ltmp. value;
+ return pos;
+ }
+ --(*pos); /* to realign *pos value! */
+
+ return clk_seq_next(s, NULL, pos);
+}
+
+static int clk_seq_show(struct seq_file *s, void *v)
+{
+ unsigned long *l = (unsigned long *)v;
+ struct list_head *node = (struct list_head *)(*l);
+ struct clk *clk = container_of(node, struct clk, node.n_node);
+ unsigned long rate = clk_get_rate(clk);
+
+ if (unlikely(!rate && !clk->parent))
+ return 0;
+
+ seq_printf(s, "%-12s\t: %ld.%02ldMHz - ", clk->name,
+ rate / 1000000, (rate % 1000000) / 10000);
+ seq_printf(s, "[0x%p]", clk);
+ if (clk_is_enabled(clk))
+ seq_printf(s, " - enabled");
+
+ if (clk->parent)
+ seq_printf(s, " - [%s]", clk->parent->name);
+ seq_printf(s, "\n");
+
+ return 0;
+}
+
+static void clk_seq_stop(struct seq_file *s, void *v)
+{
+}
+
+static const struct seq_operations clk_seq_ops = {
+ .start = clk_seq_start,
+ .next = clk_seq_next,
+ .stop = clk_seq_stop,
+ .show = clk_seq_show,
+};
+
+static int clk_proc_open(struct inode *inode, struct file *file)
+{
+ return seq_open(file, &clk_seq_ops);
+}
+
+static const struct file_operations clk_proc_ops = {
+ .owner = THIS_MODULE,
+ .open = clk_proc_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = seq_release,
+};
+
+static int __init clk_proc_init(void)
+{
+ struct proc_dir_entry *p;
+
+ p = create_proc_entry("clocks", S_IRUGO, NULL);
+
+ if (unlikely(!p))
+ return -EINVAL;
+
+ p->proc_fops = &clk_proc_ops;
+
+ return 0;
+}
+
+subsys_initcall(clk_proc_init);
+#endif
+
+#ifdef CONFIG_SYSFS
+static ssize_t clk_rate_show(struct kobject *kobj,
+ struct kobj_attribute *attr, char *buf)
+{
+ struct clk *clk = container_of(kobj, struct clk, kobj);
+
+ return sprintf(buf, "%u\n", (unsigned int)clk_get_rate(clk));
+}
+
+static ssize_t clk_rate_store(struct kobject *kobj,
+ struct kobj_attribute *attr, const char *buf, size_t count)
+{
+ unsigned long rate = simple_strtoul(buf, NULL, 10);
+ struct clk *clk = container_of(kobj, struct clk, kobj);
+
+ if (rate) {
+ if (!clk_is_enabled(clk))
+ clk_enable(clk);
+ if (clk_set_rate(clk, rate) < 0)
+ return -EINVAL;
+ } else
+ clk_disable(clk);
+ return count;
+}
+
+static const char *clk_ctrl_token[] = {
+ "auto_switching",
+ "no_auto_switching",
+ "allow_propagation",
+ "no_allow_propagation",
+ "follow_parent",
+ "no_follow_parent",
+};
+static ssize_t clk_state_show(struct kobject *kobj,
+ struct kobj_attribute *attr, char *buf)
+{
+ struct clk *clk = container_of(kobj, struct clk, kobj);
+ ssize_t ret;
+
+
+ ret = sprintf(buf, "clock name: %s\n", clk->name);
+ if (clk_is_enabled(clk))
+ ret += sprintf(buf + ret, " + enabled\n");
+ else
+ ret += sprintf(buf + ret, " + disabled\n");
+ if (clk_is_readonly(clk))
+ ret += sprintf(buf + ret, " + rate read only\n");
+ else
+ ret += sprintf(buf + ret, " + rate writable\n");
+ ret +=
+ sprintf(buf + ret, " + %s\n",
+ clk_ctrl_token[(clk_allow_propagation(clk) ? 2 : 3)]);
+ ret +=
+ sprintf(buf + ret, " + %s\n",
+ clk_ctrl_token[(clk_is_auto_switching(clk) ? 0 : 1)]);
+ ret +=
+ sprintf(buf + ret, " + %s\n",
+ clk_ctrl_token[(clk_follow_parent(clk) ? 4 : 5)]);
+ ret +=
+ sprintf(buf + ret, " + nr_clocks: %u\n", clk->nr_clocks);
+ ret +=
+ sprintf(buf + ret, " + nr_active_clocks: %u\n",
+ clk->nr_active_clocks);
+ ret +=
+ sprintf(buf + ret, " + nr_active_devices: %u\n",
+ clk->nr_active_devices);
+ ret +=
+ sprintf(buf + ret, " + rate: %u\n",
+ (unsigned int)clk_get_rate(clk));
+ return ret;
+}
+
+static ssize_t clk_ctrl_show(struct kobject *kobj,
+ struct kobj_attribute *attr, char *buf)
+{
+ int idx, ret = 0;
+
+ ret += sprintf(buf + ret, "Allowed command:\n");
+
+ for (idx = 0; idx < ARRAY_SIZE(clk_ctrl_token); ++idx)
+ ret += sprintf(buf + ret, " + %s\n", clk_ctrl_token[idx]);
+
+ return ret;
+}
+static ssize_t clk_ctrl_store(struct kobject *kobj,
+ struct kobj_attribute *attr, const char *buf, size_t count)
+{
+ int i, idx_token, ret = -EINVAL;
+ struct clk *clk = container_of(kobj, struct clk, kobj);
+
+ if (!count)
+ return ret;
+
+ for (i = 0, idx_token = -1; i < ARRAY_SIZE(clk_ctrl_token); ++i)
+ if (!strcmp(buf, clk_ctrl_token[i]))
+ idx_token = i;
+
+ if (idx_token == -EINVAL)
+ return ret; /* token not valid... */
+
+ switch (idx_token) {
+ case 0:
+ clk->flags |= CLK_EVENT_PROPAGATES;
+ break;
+ case 1:
+ clk->flags &= ~CLK_EVENT_PROPAGATES;
+ break;
+ case 2:
+ clk->flags |= CLK_AUTO_SWITCHING;
+ if (!clk->nr_active_clocks && !clk->nr_active_devices)
+ clk_disable(clk);
+ else if (clk->nr_active_clocks || clk->nr_active_devices)
+ clk_enable(clk);
+ break;
+ case 3:
+ clk->flags &= ~CLK_AUTO_SWITCHING;
+ break;
+ case 4:
+ clk->flags |= CLK_FOLLOW_PARENT;
+ break;
+ case 5:
+ clk->flags &= ~CLK_FOLLOW_PARENT;
+ break;
+ }
+
+ return count;
+}
+
+static ssize_t clk_parent_store(struct kobject *kobj,
+ struct kobj_attribute *attr, const char *buf, size_t count)
+{
+ struct clk *clk = container_of(kobj, struct clk, kobj);
+ struct clk *parent = clk_get(NULL, buf);
+
+ if (!parent)
+ return -EINVAL;
+
+ clk_put(parent);
+ clk_set_parent(clk, parent);
+
+ return count;
+}
+
+static struct kobj_attribute attributes[] = {
+__ATTR(state, S_IRUSR, clk_state_show, NULL),
+__ATTR(rate, S_IRUSR | S_IWUSR, clk_rate_show, clk_rate_store),
+__ATTR(control, S_IRUSR | S_IWUSR, clk_ctrl_show, clk_ctrl_store),
+__ATTR(parent, S_IWUSR, NULL, clk_parent_store)
+};
+
+static struct attribute *clk_attrs[] = {
+ &attributes[0].attr,
+ &attributes[1].attr,
+ &attributes[2].attr,
+ &attributes[3].attr,
+ NULL
+};
+
+static struct attribute_group clk_attr_group = {
+ .attrs = clk_attrs,
+ .name = "attributes"
+};
+
+#if 0
+static inline char *_strsep(char **s, const char *d)
+{
+ int i, len = strlen(d);
+retry:
+ if (!(*s) || !(**s))
+ return NULL;
+ for (i = 0; i < len; ++i) {
+ if (**s != *(d+i))
+ continue;
+ ++(*s);
+ goto retry;
+ }
+ return strsep(s, d);
+}
+
+/**
+ * clk_rates_store
+ *
+ * It parses the buf to create multi clocks transaction
+ * via user space
+ * The buffer has to be something like:
+ * clock_A @ rate_A; clock_B @ rate_b; clock_C @ rate_c
+ */
+static ssize_t clk_rates_store(struct kobject *kobj,
+ struct kobj_attribute *attr, const char *buf, size_t count)
+{
+ int i, ret;
+ int nclock = 0;
+ unsigned long *rates;
+ struct clk **clocks;
+
+ if (!buf)
+ return -1;
+
+ for (i = 0; i < count; ++i)
+ if (buf[i] == '@')
+ ++nclock;
+
+ rates = kmalloc(sizeof(long) * nclock, GFP_KERNEL);
+ if (!rates)
+ return -ENOMEM;
+
+ clocks = kmalloc(sizeof(void *) * nclock, GFP_KERNEL);
+ if (!clocks) {
+ ret = -ENOMEM;
+ goto err_0;
+ }
+
+ /* Parse the buffer */
+ for (i = 0; i < nclock; ++i) {
+ char *name;
+ char *nrate;
+ name = _strsep((char **)&buf, "@ "); ++buf;
+ nrate = _strsep((char **)&buf, " ;"); ++buf;
+ if (!name || !nrate) {
+ ret = -EINVAL;
+ goto err_1;
+ }
+ clocks[i] = clk_get(NULL, name);
+ rates[i] = simple_strtoul(nrate, NULL, 10);
+ if (!clocks[i]) { /* the clock doesn't exist! */
+ ret = -EINVAL;
+ goto err_1;
+ }
+ }
+
+ ret = clk_set_rates(clocks, rates, nclock);
+ if (ret >= 0)
+ ret = count; /* to say OK */
+
+err_1:
+ kfree(clocks);
+err_0:
+ kfree(rates);
+ return ret;
+}
+
+static struct kobj_attribute clk_rates_attr =
+ __ATTR(rates, S_IWUSR, NULL, clk_rates_store);
+#endif
+
+static int __init clk_add_attributes(struct clk *clk, void *data)
+{
+ int ret;
+
+ ret = sysfs_update_group(&clk->kobj, &clk_attr_group);
+
+ return ret;
+}
+
+static int __init clk_late_init(void)
+{
+ int ret;
+
+ ret = clk_for_each(clk_add_attributes, NULL);
+
+ return ret;
+}
+
+late_initcall(clk_late_init);
+#endif
diff --git a/drivers/base/init.c b/drivers/base/init.c
index 7bd9b6a..2441b26 100644
--- a/drivers/base/init.c
+++ b/drivers/base/init.c
@@ -24,6 +24,7 @@ void __init driver_init(void)
buses_init();
classes_init();
firmware_init();
+ clock_init();
hypervisor_init();
/* These are also core pieces, but must come after the
diff --git a/drivers/base/platform.c b/drivers/base/platform.c
index 8b4708e..550d993 100644
--- a/drivers/base/platform.c
+++ b/drivers/base/platform.c
@@ -17,6 +17,8 @@
#include <linux/bootmem.h>
#include <linux/err.h>
#include <linux/slab.h>
+#include <linux/clk.h>
+#include "clk.h"
#include "base.h"
@@ -272,9 +274,20 @@ int platform_device_add(struct platform_device *pdev)
pr_debug("Registering platform device '%s'. Parent at %s\n",
dev_name(&pdev->dev), dev_name(pdev->dev.parent));
+#ifdef CONFIG_GENERIC_CLK_FM
+ clk_add_device(pdev, PDEV_ADDING);
+
+ ret = device_add(&pdev->dev);
+
+ clk_add_device(pdev, (ret ? PDEV_ADD_FAILED : PDEV_ADDED));
+
+ if (ret == 0)
+ return ret;
+#else
ret = device_add(&pdev->dev);
if (ret == 0)
return ret;
+#endif
failed:
while (--i >= 0) {
@@ -311,6 +324,9 @@ void platform_device_del(struct platform_device *pdev)
if (type == IORESOURCE_MEM || type == IORESOURCE_IO)
release_resource(r);
}
+#ifdef CONFIG_GENERIC_CLK_FM
+ clk_del_device(pdev);
+#endif
}
}
EXPORT_SYMBOL_GPL(platform_device_del);
@@ -445,7 +461,18 @@ static int platform_drv_probe(struct device *_dev)
struct platform_driver *drv = to_platform_driver(_dev->driver);
struct platform_device *dev = to_platform_device(_dev);
+#ifdef CONFIG_GENERIC_CLK_FM
+ int ret;
+ ret = clk_probe_device(dev, PDEV_PROBEING);
+ if (ret)
+ return ret;
+ ret = drv->probe(dev);
+
+ clk_probe_device(dev, (ret ? PDEV_PROBE_FAILED : PDEV_PROBED));
+ return ret;
+#else
return drv->probe(dev);
+#endif
}
static int platform_drv_probe_fail(struct device *_dev)
diff --git a/include/linux/clk.h b/include/linux/clk.h
index 1db9bbf..e537bcd 100644
--- a/include/linux/clk.h
+++ b/include/linux/clk.h
@@ -12,6 +12,7 @@
#define __LINUX_CLK_H
struct device;
+struct platform_device;
/*
* The base API.
@@ -142,4 +143,254 @@ struct clk *clk_get_parent(struct clk *clk);
*/
struct clk *clk_get_sys(const char *dev_id, const char *con_id);
+/**
+ * clk_set_rates - set the clock rates
+ * @clk: clocks source
+ * @rate: desired clock rates in Hz
+ * @nclks: the number of clocks
+ *
+ * Returns success (0) or negative errno.
+ */
+int clk_set_rates(struct clk **clk, unsigned long *rates, unsigned long nclks);
+
+#ifndef CONFIG_GENERIC_CLK_FM
+
+#define bind_clock(_clk)
+#define pdevice_setclock(_dev, _clk)
+#define pdevice_setclock_byname(_dev, _clkname)
+#define pdevice_num_clocks(_dev)
+#define pdevice_clock(dev, idx)
+
+#else
+
+#include <linux/kobject.h>
+#include <linux/klist.h>
+#include <linux/notifier.h>
+#include <linux/pm.h>
+#include <linux/spinlock.h>
+#include <asm/atomic.h>
+
+
+/**
+ * Clock operation -
+ *
+ * It's a set of function pointer to identify all the capability on a clock
+ */
+struct clk_ops {
+/** @init initializes the clock */
+ int (*init)(struct clk *);
+/** @enable enables the clock */
+ int (*enable)(struct clk *);
+/** @disable disables the clock */
+ int (*disable)(struct clk *);
+/** @set_rate sets the new frequency rate */
+ int (*set_rate)(struct clk *, unsigned long value);
+/** @set_parent sets the new parent clock */
+ int (*set_parent)(struct clk *clk, struct clk *parent);
+/** @recalc updates the clock rate when the parent clock is updated */
+ void (*recalc)(struct clk *);
+/** @round returns the allowed rate on the required value */
+ unsigned long (*round)(struct clk *, unsigned long value);
+/** @eval evaluates the clock rate based on a parent_rate but the
+ * real clock rate is __not__ changed
+ */
+ unsigned long (*eval)(struct clk *, unsigned long parent_rate);
+};
+
+/**
+ * struct clk - clock object
+ */
+struct clk {
+ spinlock_t lock;
+
+ struct kobject kobj;
+ struct kobject *kdevices;
+
+ int id;
+
+ const char *name;
+ struct module *owner;
+
+ struct clk *parent;
+ struct clk_ops *ops;
+
+ void *private_data;
+
+ unsigned long rate;
+ unsigned long flags;
+
+ unsigned int nr_active_clocks;
+ unsigned int nr_active_devices;
+ unsigned int nr_clocks;
+
+ void *towner;/* the transaction owner of the clock */
+
+ struct klist childs;
+ struct klist devices;
+
+ struct klist_node node; /* for global link */
+ struct klist_node child_node; /* for child link */
+};
+
+#define CLK_ALWAYS_ENABLED (0x1 << 0)
+#define CLK_EVENT_PROPAGATES (0x1 << 1)
+#define CLK_RATE_PROPAGATES CLK_EVENT_PROPAGATES
+/* CLK_AUTO_SWITCHING: enable/disable the clock based on the
+ * current active children
+ */
+#define CLK_AUTO_SWITCHING (0x1 << 2)
+/* CLK_FOLLOW_PARENT: enable/disable the clock as the parent is
+ * enabled/disabled
+ */
+#define CLK_FOLLOW_PARENT (0x1 << 3)
+
+/*
+ * Flags to support the system standby
+ */
+#define CLK_PM_EXP_SHIFT (24)
+#define CLK_PM_EXP_NRBITS (7)
+#define CLK_PM_RATIO_SHIFT (16)
+#define CLK_PM_RATIO_NRBITS (8)
+#define CLK_PM_EDIT_SHIFT (31)
+#define CLK_PM_EDIT_NRBITS (1)
+#define CLK_PM_TURNOFF (((1<<CLK_PM_EXP_NRBITS)-1) << CLK_PM_EXP_SHIFT)
+
+int early_clk_register(struct clk *);
+/**
+ * Registers a new clock into the system
+ */
+int clk_register(struct clk *);
+/**
+ * Unregisters a clock into the system
+ */
+int clk_unregister(struct clk *);
+
+/**
+ * Returns the clock rate if the parent clock is 'parent_rate'
+ */
+unsigned long clk_evaluate_rate(struct clk *, unsigned long parent_rate);
+
+#define CLK_UNDEFINED_RATE (-1UL)
+/**
+ * Utility functions in the clock framework
+ */
+int clk_for_each(int (*fn)(struct clk *, void *), void *);
+
+int clk_for_each_child(struct clk *, int (*fn)(struct clk *, void *), void *);
+
+/** struct pdev_clk_info -
+ *
+ * It's a meta data used to link the device of linux driver model
+ * to the clock framework.
+ * The device driver developers has to set only the clk field
+ * all the other fileds are managed in the clk core code
+ */
+struct pdev_clk_info {
+ /** the device owner */
+ struct platform_device *pdev;
+ /** the clock address */
+ struct clk *clk;
+ /** used by the clock core*/
+ struct klist_node node;
+};
+
+/******************** clk transition notifiers *******************/
+#define NOTIFY_CLK_ENTERCHANGE 0x1
+#define NOTIFY_CLK_PRECHANGE 0x2
+#define NOTIFY_CLK_POSTCHANGE 0x4
+#define NOTIFY_CLK_EXITCHANGE 0x8
+
+/** struct clk_event
+ *
+ * It's the object propagated during a clock transaction.
+ * During a transaction each device will receive an array of 'struct clk_event'
+ * based on the clocks it uses
+ */
+struct clk_event {
+ /** on which clock the event is */
+ struct clk *clk;
+ /** the clock rate before the event */
+ unsigned long old_rate;
+ /** the clock rate after the event */
+ unsigned long new_rate;
+};
+
+enum clk_event_e {
+ _CLK_NOCHANGE,
+ _CLK_ENABLE,
+ _CLK_DISABLE,
+ _CLK_CHANGE
+};
+
+/**
+ * clk_event_decode -
+ *
+ * @event: the events has to be decoded
+ * It's an utility function to identify what each clock
+ * is doing
+ */
+static inline enum clk_event_e clk_event_decode(struct clk_event const *event)
+{
+ if (event->old_rate == event->new_rate)
+ return _CLK_NOCHANGE;
+ if (!event->old_rate && event->new_rate)
+ return _CLK_ENABLE;
+ if (event->old_rate && !event->new_rate)
+ return _CLK_DISABLE;
+ return _CLK_CHANGE;
+}
+
+enum notify_ret_e {
+ NOTIFY_EVENT_HANDLED = 0, /* event handled */
+ NOTIFY_EVENT_NOTHANDLED, /* event not handled */
+};
+
+/* Some macro device oriented static initialization */
+#define bind_clock(_clk) \
+ .nr_clks = 1, \
+ .clks = (struct pdev_clk_info[]) { { \
+ .clk = (_clk), \
+ } },
+
+#define pdevice_setclock(_dev, _clk) \
+ (_dev)->clks[0].clk = (_clk); \
+ (_dev)->nr_clks = 1;
+
+#define pdevice_setclock_byname(_dev, _clkname) \
+ (_dev)->clks[0].clk = clk_get(NULL, _clkname); \
+ (_dev)->nr_clks = 1;
+
+#define pdevice_num_clocks(_dev) ((_dev)->nr_clks)
+
+#define pdevice_clock(dev, idx) ((dev)->clks[(idx)].clk)
+
+/**
+ * clk_generic_notify -
+ *
+ * @code: the code event
+ * @dev: the platform_device under transaction
+ * @data: the clock event descriptor
+ *
+ * it's a generic notify function for devie with _only_
+ * one clock. It will :
+ * - accept every 'ENTER' state
+ * - suspend on 'PRE' state
+ * - resume on 'POST' state
+ * - do nothing on 'EXIT' state
+ */
+int clk_generic_notify(unsigned long code, struct platform_device *dev,
+ void *data);
+
+/*
+ * clk_generic_evaluate_rate
+ *
+ * @clk: the analised clock
+ * @prate: the parent rate
+ *
+ * Evaluate the clock rate (without hardware modification) based on a 'prate'
+ * parent clock rate. It's based on 'divisor' relationship
+ * between parent and child
+ */
+unsigned long clk_generic_evaluate_rate(struct clk *clk, unsigned long prate);
+#endif
#endif
diff --git a/include/linux/platform_device.h b/include/linux/platform_device.h
index b67bb5d..db1989d 100644
--- a/include/linux/platform_device.h
+++ b/include/linux/platform_device.h
@@ -12,6 +12,7 @@
#define _PLATFORM_DEVICE_H_
#include <linux/device.h>
+#include <linux/clk.h>
#include <linux/mod_devicetable.h>
struct platform_device {
@@ -22,6 +23,11 @@ struct platform_device {
struct resource * resource;
struct platform_device_id *id_entry;
+#ifdef CONFIG_GENERIC_CLK_FM
+ unsigned long clk_state; /* used by the core */
+ unsigned long nr_clks;
+ struct pdev_clk_info *clks;
+#endif
};
#define platform_get_device_id(pdev) ((pdev)->id_entry)
@@ -61,6 +67,9 @@ struct platform_driver {
int (*resume_early)(struct platform_device *);
int (*resume)(struct platform_device *);
struct device_driver driver;
+#ifdef CONFIG_GENERIC_CLK_FM
+ int (*notify)(unsigned long code, struct platform_device *, void *);
+#endif
struct platform_device_id *id_table;
};
diff --git a/init/Kconfig b/init/Kconfig
index 0682ecc..4254c5f 100644
--- a/init/Kconfig
+++ b/init/Kconfig
@@ -1042,6 +1042,29 @@ config SLOW_WORK
See Documentation/slow-work.txt.
+config GENERIC_CLK_FM
+ default n
+ depends on EXPERIMENTAL
+ bool "Generic Clock Framework"
+ help
+ Add the clock framework in the Linux driver model
+ to track the clocks used by each devices and drivers
+
+config CLK_FORCE_GENERIC_EVALUATE
+ depends on GENERIC_CLK_FM
+ default n
+ bool "Force the clk_generic_evaluate_rate"
+ help
+ Say the if you want use the clk_generic_evaluate_rate on every clock
+ without evaluate_rate
+
+config CLK_DEBUG
+ depends on GENERIC_CLK_FM
+ default n
+ bool "Debug the Generic Clk Framework"
+ help
+ Prints some message to debug the clock framework
+
endmenu # General setup
config HAVE_GENERIC_DMA_COHERENT
--
1.6.2.5
--------------030909070008040302040602--
More information about the linux-arm-kernel
mailing list