From: Francesco VIRLINZI <francesco.virlinzi@st.com>
To: Linux-sh <linux-sh@vger.kernel.org>, linux-embedded@vger.kernel.org
Subject: [Proposal] [PATCH] generic clock framework
Date: Tue, 10 Nov 2009 16:06:29 +0100 [thread overview]
Message-ID: <4AF98175.4090205@st.com> (raw)
[-- Attachment #1: Type: text/plain, Size: 1842 bytes --]
Hi all
I'm Francesco and I work in STMicroelectronics
In the last ELC-E_2009 I spoke on a generic clock framework I'm working on
(see
http://tree.celinuxforum.org/CelfPubWiki/ELCEurope2009Presentations?action=AttachFile&do=view&target=ELC_E_2009_Generic_Clock_Framework.pdf).
I wrote the gcf to manage both clocks the platform_devices during a
clock operation.
The main features are:
- it's integrated in the LDM
- it tracks the clock-to-clock relationship
- it tracks the clock-to-device relationship
- it has sysfs interface
- - the user can navigate the clock tree under /sys/clocks/...
- it uses the linux API (<linux/clk.h>) with some extra functions (to
register/unregister a clock
and other utility functions as clk_for_each())
- it involves the platform_device and the platform_driver in the clock
propagation.
- - basically each clock operation is managed as a transaction which
evolves step by step.
- - all the clock rates are evaluated (before the clk operation is
actually done)
- - each platform_device can check (before the clk operation is
actually done) the clk environment
it will have at the end of clock operation and if required it can
reject the operation.
- - each clock operation is actually executed only if all the
platform_devices accept the operation it-self
Moreover a common clock framework could be used to avoid a lot of
duplicated and/or similar code
just a grep of 'EXPORT_SYMBOL\(clk_enable' under arch/arm finds 22
entries.
The patch is based on a 2.6.30 kernel also if it has a preliminary
integration with the PM_RUNTIME
support.
It works on our st40 (an sh4 cpu based system) no test/porting was done
on any ARM platform.
It would be mainly a starting point for a discussion and I'm available
to extend/fix/share it.
Regards
Francesco
[-- Attachment #2: 0001-generic-clock-framework.patch --]
[-- Type: text/plain, Size: 77606 bytes --]
From 4e065fb9247ec511bfdc88001f0713977d3f4e89 Mon Sep 17 00:00:00 2001
From: Francesco Virlinzi <francesco.virlinzi@st.com>
Date: Fri, 23 Oct 2009 15:26:42 +0200
Subject: [PATCH] generic clock framework
version: 0.6.2
Signed-off-by: Francesco Virlinzi <francesco.virlinzi@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@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@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@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@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
WARNING: multiple messages have this Message-ID (diff)
From: Francesco VIRLINZI <francesco.virlinzi@st.com>
To: linux-sh@vger.kernel.org
Subject: [Proposal] [PATCH] generic clock framework
Date: Tue, 10 Nov 2009 15:06:29 +0000 [thread overview]
Message-ID: <4AF98175.4090205@st.com> (raw)
[-- Attachment #1: Type: text/plain, Size: 1842 bytes --]
Hi all
I'm Francesco and I work in STMicroelectronics
In the last ELC-E_2009 I spoke on a generic clock framework I'm working on
(see
http://tree.celinuxforum.org/CelfPubWiki/ELCEurope2009Presentations?action=AttachFile&do=view&target=ELC_E_2009_Generic_Clock_Framework.pdf).
I wrote the gcf to manage both clocks the platform_devices during a
clock operation.
The main features are:
- it's integrated in the LDM
- it tracks the clock-to-clock relationship
- it tracks the clock-to-device relationship
- it has sysfs interface
- - the user can navigate the clock tree under /sys/clocks/...
- it uses the linux API (<linux/clk.h>) with some extra functions (to
register/unregister a clock
and other utility functions as clk_for_each())
- it involves the platform_device and the platform_driver in the clock
propagation.
- - basically each clock operation is managed as a transaction which
evolves step by step.
- - all the clock rates are evaluated (before the clk operation is
actually done)
- - each platform_device can check (before the clk operation is
actually done) the clk environment
it will have at the end of clock operation and if required it can
reject the operation.
- - each clock operation is actually executed only if all the
platform_devices accept the operation it-self
Moreover a common clock framework could be used to avoid a lot of
duplicated and/or similar code
just a grep of 'EXPORT_SYMBOL\(clk_enable' under arch/arm finds 22
entries.
The patch is based on a 2.6.30 kernel also if it has a preliminary
integration with the PM_RUNTIME
support.
It works on our st40 (an sh4 cpu based system) no test/porting was done
on any ARM platform.
It would be mainly a starting point for a discussion and I'm available
to extend/fix/share it.
Regards
Francesco
[-- Attachment #2: 0001-generic-clock-framework.patch --]
[-- Type: text/plain, Size: 77606 bytes --]
From 4e065fb9247ec511bfdc88001f0713977d3f4e89 Mon Sep 17 00:00:00 2001
From: Francesco Virlinzi <francesco.virlinzi@st.com>
Date: Fri, 23 Oct 2009 15:26:42 +0200
Subject: [PATCH] generic clock framework
version: 0.6.2
Signed-off-by: Francesco Virlinzi <francesco.virlinzi@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@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@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@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@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
next reply other threads:[~2009-11-10 15:06 UTC|newest]
Thread overview: 9+ messages / expand[flat|nested] mbox.gz Atom feed top
2009-11-10 15:06 Francesco VIRLINZI [this message]
2009-11-10 15:06 ` [Proposal] [PATCH] generic clock framework Francesco VIRLINZI
2009-11-10 18:08 ` Tim Bird
2009-11-10 18:08 ` Tim Bird
2009-11-11 7:54 ` Francesco VIRLINZI
2009-11-11 7:54 ` Francesco VIRLINZI
2009-11-11 7:54 ` Francesco VIRLINZI
-- strict thread matches above, loose matches on Subject: below --
2009-11-10 14:00 Francesco VIRLINZI
2009-11-10 14:00 ` Francesco VIRLINZI
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=4AF98175.4090205@st.com \
--to=francesco.virlinzi@st.com \
--cc=linux-embedded@vger.kernel.org \
--cc=linux-sh@vger.kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.