From patchwork Tue Nov 10 15:06:29 2009 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Francesco VIRLINZI X-Patchwork-Id: 59068 Received: from vger.kernel.org (vger.kernel.org [209.132.176.167]) by demeter.kernel.org (8.14.2/8.14.2) with ESMTP id nAAF6pal000508 for ; Tue, 10 Nov 2009 15:06:51 GMT Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1751031AbZKJPGl (ORCPT ); Tue, 10 Nov 2009 10:06:41 -0500 Received: (majordomo@vger.kernel.org) by vger.kernel.org id S1756515AbZKJPGk (ORCPT ); Tue, 10 Nov 2009 10:06:40 -0500 Received: from eu1sys200aog102.obsmtp.com ([207.126.144.113]:52990 "EHLO eu1sys200aog102.obsmtp.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1756653AbZKJPGf (ORCPT ); Tue, 10 Nov 2009 10:06:35 -0500 Received: from source ([164.129.1.35]) (using TLSv1) by eu1sys200aob102.postini.com ([207.126.147.11]) with SMTP ID DSNKSvmBfUv1tLxNRDee/AePn9pDduJ94/QU@postini.com; Tue, 10 Nov 2009 15:06:40 UTC Received: from zeta.dmz-eu.st.com (ns2.st.com [164.129.230.9]) by beta.dmz-eu.st.com (STMicroelectronics) with ESMTP id 7F06119B; Tue, 10 Nov 2009 15:06:33 +0000 (GMT) Received: from mail1.ctn.st.com (mail1.ctn.st.com [164.130.116.128]) by zeta.dmz-eu.st.com (STMicroelectronics) with ESMTP id DA99DCBF; Tue, 10 Nov 2009 15:06:32 +0000 (GMT) Received: from [10.52.139.41] (mdt-dhcp41.ctn.st.com [10.52.139.41]) by mail1.ctn.st.com (MOS 3.8.7a) with ESMTP id DEE51693 (AUTH virlinzi); Tue, 10 Nov 2009 16:06:39 +0100 (CET) Message-ID: <4AF98175.4090205@st.com> Date: Tue, 10 Nov 2009 16:06:29 +0100 From: Francesco VIRLINZI User-Agent: Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.1.4pre) Gecko/20091014 Fedora/3.0-2.8.b4.fc11 Thunderbird/3.0b4 MIME-Version: 1.0 To: Linux-sh , linux-embedded@vger.kernel.org Subject: [Proposal] [PATCH] generic clock framework Sender: linux-sh-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-sh@vger.kernel.org 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 + * ------------------------------------------------------------------------- + * May be copied or modified under the terms of the GNU General Public + * License v.2 ONLY. See linux/COPYING for more information. + * + * ------------------------------------------------------------------------- + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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 + ---------------------------------------------------------------------------- + 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 +#include +#include +#include +#include +#include +#include + +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 + * ------------------------------------------------------------------------- + * May be copied or modified under the terms of the GNU General Public + * License v.2 ONLY. See linux/COPYING for more information. + * + * ------------------------------------------------------------------------- + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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 + * ------------------------------------------------------------------------- + * May be copied or modified under the terms of the GNU General Public + * License v.2 ONLY. See linux/COPYING for more information. + * + * ------------------------------------------------------------------------- + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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 +#include +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 #include #include +#include +#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 +#include +#include +#include +#include +#include + + +/** + * 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<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 +#include #include 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