[ARM/tegra] Add Tegra3 support
[linux-2.6.git] / arch / arm / mach-tegra / clock.c
index f55bb83..0096f6f 100644 (file)
 
 #include <linux/kernel.h>
 #include <linux/clk.h>
-#include <linux/list.h>
+#include <linux/clkdev.h>
+#include <linux/debugfs.h>
+#include <linux/delay.h>
 #include <linux/init.h>
+#include <linux/list.h>
 #include <linux/module.h>
-#include <linux/debugfs.h>
-#include <linux/slab.h>
 #include <linux/seq_file.h>
-#include <linux/regulator/consumer.h>
-#include <linux/clkdev.h>
+#include <linux/slab.h>
+#include <linux/uaccess.h>
+
+#include <mach/clk.h>
 
-#include "clock.h"
 #include "board.h"
-#include "fuse.h"
+#include "clock.h"
+#include "dvfs.h"
 
+/*
+ * Locking:
+ *
+ * Each struct clk has a lock.  Depending on the cansleep flag, that lock
+ * may be a spinlock or a mutex.  For most clocks, the spinlock is sufficient,
+ * and using the spinlock allows the clock to be manipulated from an interrupt
+ * or while holding a spinlock.  Some clocks may need to adjust a regulator
+ * in order to maintain the required voltage for a new frequency.  Those
+ * clocks set the cansleep flag, and take a mutex so that the regulator api
+ * can be used while holding the lock.
+ *
+ * To avoid AB-BA locking problems, locks must always be traversed from child
+ * clock to parent clock.  For example, when enabling a clock, the clock's lock
+ * is taken, and then clk_enable is called on the parent, which take's the
+ * parent clock's lock.  There are two exceptions to this ordering:
+ *  1. When setting a clock as cansleep, in which case the entire list of clocks
+ *     is traversed to set the children as cansleep as well.  This must occur
+ *     during init, before any calls to clk_get, so no other clock locks can
+ *     get taken.
+ *  2. When dumping the clock tree through debugfs.  In this case, clk_lock_all
+ *     is called, which attemps to iterate through the entire list of clocks
+ *     and take every clock lock.  If any call to clk_trylock fails, a locked
+ *     clocks are unlocked, and the process is retried.  When all the locks
+ *     are held, the only clock operation that can be called is
+ *     clk_get_rate_all_locked.
+ *
+ * Within a single clock, no clock operation can call another clock operation
+ * on itself, except for clk_get_rate_locked and clk_set_rate_locked.  Any
+ * clock operation can call any other clock operation on any of it's possible
+ * parents.
+ *
+ * clk_set_cansleep is used to mark a clock as sleeping.  It is called during
+ * dvfs (Dynamic Voltage and Frequency Scaling) init on any clock that has a
+ * dvfs requirement.  It can only be called on clocks that are the sole parent
+ * of all of their child clocks, meaning the child clock can not be reparented
+ * onto a different, possibly non-sleeping, clock.  This is inherently true
+ * of all leaf clocks in the clock tree
+ *
+ * An additional mutex, clock_list_lock, is used to protect the list of all
+ * clocks.
+ *
+ * The clock operations must lock internally to protect against
+ * read-modify-write on registers that are shared by multiple clocks
+ */
+static DEFINE_MUTEX(clock_list_lock);
 static LIST_HEAD(clocks);
 
-static DEFINE_SPINLOCK(clock_lock);
-static DEFINE_MUTEX(dvfs_lock);
-
-static int clk_is_dvfs(struct clk *c)
-{
-       return (c->dvfs != NULL);
-};
-
-static int dvfs_set_rate(struct dvfs *d, unsigned long rate)
+struct clk *tegra_get_clock_by_name(const char *name)
 {
-       struct dvfs_table *t;
-
-       if (d->table == NULL)
-               return -ENODEV;
-
-       for (t = d->table; t->rate != 0; t++) {
-               if (rate <= t->rate) {
-                       if (!d->reg)
-                               return 0;
-
-                       return regulator_set_voltage(d->reg,
-                               t->millivolts * 1000,
-                               d->max_millivolts * 1000);
+       struct clk *c;
+       struct clk *ret = NULL;
+       mutex_lock(&clock_list_lock);
+       list_for_each_entry(c, &clocks, node) {
+               if (strcmp(c->name, name) == 0) {
+                       ret = c;
+                       break;
                }
        }
-
-       return -EINVAL;
+       mutex_unlock(&clock_list_lock);
+       return ret;
 }
 
-static void dvfs_init(struct clk *c)
+/* Must be called with clk_lock(c) held */
+static unsigned long clk_predict_rate_from_parent(struct clk *c, struct clk *p)
 {
-       int process_id;
-       int i;
-       struct dvfs_table *table;
+       u64 rate;
 
-       process_id = c->dvfs->cpu ? tegra_core_process_id() :
-               tegra_cpu_process_id();
+       rate = clk_get_rate(p);
 
-       for (i = 0; i < c->dvfs->process_id_table_length; i++)
-               if (process_id == c->dvfs->process_id_table[i].process_id)
-                       c->dvfs->table = c->dvfs->process_id_table[i].table;
+       if (c->ops && c->ops->recalculate_rate)
+               c->ops->recalculate_rate(c);
 
-       if (c->dvfs->table == NULL) {
-               pr_err("Failed to find dvfs table for clock %s process %d\n",
-                       c->name, process_id);
-               return;
+       if (c->mul != 0 && c->div != 0) {
+               rate *= c->mul;
+               rate += c->div - 1; /* round up */
+               do_div(rate, c->div);
        }
 
-       c->dvfs->max_millivolts = 0;
-       for (table = c->dvfs->table; table->rate != 0; table++)
-               if (c->dvfs->max_millivolts < table->millivolts)
-                       c->dvfs->max_millivolts = table->millivolts;
+       return rate;
+}
 
-       c->dvfs->reg = regulator_get(NULL, c->dvfs->reg_id);
+/* Must be called with clk_lock(c) held */
+unsigned long clk_get_rate_locked(struct clk *c)
+{
+       unsigned long rate;
 
-       if (IS_ERR(c->dvfs->reg)) {
-               pr_err("Failed to get regulator %s for clock %s\n",
-                       c->dvfs->reg_id, c->name);
-               c->dvfs->reg = NULL;
-               return;
-       }
+       if (c->parent)
+               rate = clk_predict_rate_from_parent(c, c->parent);
+       else
+               rate = c->rate;
 
-       if (c->refcnt > 0)
-               dvfs_set_rate(c->dvfs, c->rate);
+       return rate;
 }
 
-struct clk *tegra_get_clock_by_name(const char *name)
+unsigned long clk_get_rate(struct clk *c)
 {
-       struct clk *c;
-       struct clk *ret = NULL;
        unsigned long flags;
-       spin_lock_irqsave(&clock_lock, flags);
-       list_for_each_entry(c, &clocks, node) {
-               if (strcmp(c->name, name) == 0) {
-                       ret = c;
-                       break;
-               }
-       }
-       spin_unlock_irqrestore(&clock_lock, flags);
-       return ret;
+       unsigned long rate;
+
+       clk_lock_save(c, &flags);
+
+       rate = clk_get_rate_locked(c);
+
+       clk_unlock_restore(c, &flags);
+
+       return rate;
 }
+EXPORT_SYMBOL(clk_get_rate);
 
-static void clk_recalculate_rate(struct clk *c)
+static void __clk_set_cansleep(struct clk *c)
 {
-       u64 rate;
+       struct clk *child;
+       BUG_ON(mutex_is_locked(&c->mutex));
+       BUG_ON(spin_is_locked(&c->spinlock));
 
-       if (!c->parent)
-               return;
+       list_for_each_entry(child, &clocks, node) {
+               if (child->parent != c)
+                       continue;
 
-       rate = c->parent->rate;
+               WARN(child->ops && child->ops->set_parent,
+                       "can't make child clock %s of %s "
+                       "sleepable if it's parent could change",
+                       child->name, c->name);
 
-       if (c->mul != 0 && c->div != 0) {
-               rate = rate * c->mul;
-               do_div(rate, c->div);
+               __clk_set_cansleep(child);
        }
 
-       if (rate > c->max_rate)
-               pr_warn("clocks: Set clock %s to rate %llu, max is %lu\n",
-                       c->name, rate, c->max_rate);
+       c->cansleep = true;
+}
 
-       c->rate = rate;
+/* Must be called before any clk_get calls */
+void clk_set_cansleep(struct clk *c)
+{
+
+       mutex_lock(&clock_list_lock);
+       __clk_set_cansleep(c);
+       mutex_unlock(&clock_list_lock);
 }
 
 int clk_reparent(struct clk *c, struct clk *parent)
 {
        c->parent = parent;
-       list_del(&c->sibling);
-       list_add_tail(&c->sibling, &parent->children);
        return 0;
 }
 
-static void propagate_rate(struct clk *c)
-{
-       struct clk *clkp;
-
-       list_for_each_entry(clkp, &c->children, sibling) {
-               clk_recalculate_rate(clkp);
-               propagate_rate(clkp);
-       }
-}
-
 void clk_init(struct clk *c)
 {
-       unsigned long flags;
-
-       spin_lock_irqsave(&clock_lock, flags);
-
-       INIT_LIST_HEAD(&c->children);
-       INIT_LIST_HEAD(&c->sibling);
+       clk_lock_init(c);
 
        if (c->ops && c->ops->init)
                c->ops->init(c);
 
-       clk_recalculate_rate(c);
+       if (!c->ops || !c->ops->enable) {
+               c->refcnt++;
+               c->set = true;
+               if (c->parent)
+                       c->state = c->parent->state;
+               else
+                       c->state = ON;
+       }
 
+       mutex_lock(&clock_list_lock);
        list_add(&c->node, &clocks);
-
-       if (c->parent)
-               list_add_tail(&c->sibling, &c->parent->children);
-
-       spin_unlock_irqrestore(&clock_lock, flags);
+       mutex_unlock(&clock_list_lock);
 }
 
-int clk_enable_locked(struct clk *c)
+int clk_enable(struct clk *c)
 {
-       int ret;
+       int ret = 0;
+       unsigned long flags;
+
+       clk_lock_save(c, &flags);
+
+       if (clk_is_auto_dvfs(c)) {
+               ret = tegra_dvfs_set_rate(c, clk_get_rate_locked(c));
+               if (ret)
+                       goto out;
+       }
 
        if (c->refcnt == 0) {
                if (c->parent) {
-                       ret = clk_enable_locked(c->parent);
+                       ret = clk_enable(c->parent);
                        if (ret)
-                               return ret;
+                               goto out;
                }
 
                if (c->ops && c->ops->enable) {
                        ret = c->ops->enable(c);
                        if (ret) {
                                if (c->parent)
-                                       clk_disable_locked(c->parent);
-                               return ret;
+                                       clk_disable(c->parent);
+                               goto out;
                        }
                        c->state = ON;
-#ifdef CONFIG_DEBUG_FS
-                       c->set = 1;
-#endif
+                       c->set = true;
                }
        }
        c->refcnt++;
-
-       return 0;
-}
-
-int clk_enable_cansleep(struct clk *c)
-{
-       int ret;
-       unsigned long flags;
-
-       mutex_lock(&dvfs_lock);
-
-       if (clk_is_dvfs(c) && c->refcnt > 0)
-               dvfs_set_rate(c->dvfs, c->rate);
-
-       spin_lock_irqsave(&clock_lock, flags);
-       ret = clk_enable_locked(c);
-       spin_unlock_irqrestore(&clock_lock, flags);
-
-       mutex_unlock(&dvfs_lock);
-
+out:
+       clk_unlock_restore(c, &flags);
        return ret;
 }
-EXPORT_SYMBOL(clk_enable_cansleep);
+EXPORT_SYMBOL(clk_enable);
 
-int clk_enable(struct clk *c)
+void clk_disable(struct clk *c)
 {
-       int ret;
        unsigned long flags;
 
-       if (clk_is_dvfs(c))
-               BUG();
-
-       spin_lock_irqsave(&clock_lock, flags);
-       ret = clk_enable_locked(c);
-       spin_unlock_irqrestore(&clock_lock, flags);
+       clk_lock_save(c, &flags);
 
-       return ret;
-}
-EXPORT_SYMBOL(clk_enable);
-
-void clk_disable_locked(struct clk *c)
-{
        if (c->refcnt == 0) {
                WARN(1, "Attempting to disable clock %s with refcnt 0", c->name);
+               clk_unlock_restore(c, &flags);
                return;
        }
        if (c->refcnt == 1) {
@@ -252,69 +254,53 @@ void clk_disable_locked(struct clk *c)
                        c->ops->disable(c);
 
                if (c->parent)
-                       clk_disable_locked(c->parent);
+                       clk_disable(c->parent);
 
                c->state = OFF;
        }
        c->refcnt--;
-}
-
-void clk_disable_cansleep(struct clk *c)
-{
-       unsigned long flags;
-
-       mutex_lock(&dvfs_lock);
 
-       spin_lock_irqsave(&clock_lock, flags);
-       clk_disable_locked(c);
-       spin_unlock_irqrestore(&clock_lock, flags);
+       if (clk_is_auto_dvfs(c) && c->refcnt == 0)
+               tegra_dvfs_set_rate(c, 0);
 
-       if (clk_is_dvfs(c) && c->refcnt == 0)
-               dvfs_set_rate(c->dvfs, c->rate);
-
-       mutex_unlock(&dvfs_lock);
+       clk_unlock_restore(c, &flags);
 }
-EXPORT_SYMBOL(clk_disable_cansleep);
+EXPORT_SYMBOL(clk_disable);
 
-void clk_disable(struct clk *c)
+int clk_set_parent(struct clk *c, struct clk *parent)
 {
+       int ret = 0;
        unsigned long flags;
+       unsigned long new_rate;
+       unsigned long old_rate;
 
-       if (clk_is_dvfs(c))
-               BUG();
+       clk_lock_save(c, &flags);
 
-       spin_lock_irqsave(&clock_lock, flags);
-       clk_disable_locked(c);
-       spin_unlock_irqrestore(&clock_lock, flags);
-}
-EXPORT_SYMBOL(clk_disable);
+       if (!c->ops || !c->ops->set_parent) {
+               ret = -ENOSYS;
+               goto out;
+       }
 
-int clk_set_parent_locked(struct clk *c, struct clk *parent)
-{
-       int ret;
+       new_rate = clk_predict_rate_from_parent(c, parent);
+       old_rate = clk_get_rate_locked(c);
 
-       if (!c->ops || !c->ops->set_parent)
-               return -ENOSYS;
+       if (clk_is_auto_dvfs(c) && c->refcnt > 0 &&
+                       (!c->parent || new_rate > old_rate)) {
+               ret = tegra_dvfs_set_rate(c, new_rate);
+               if (ret)
+                       goto out;
+       }
 
        ret = c->ops->set_parent(c, parent);
-
        if (ret)
-               return ret;
-
-       clk_recalculate_rate(c);
+               goto out;
 
-       propagate_rate(c);
+       if (clk_is_auto_dvfs(c) && c->refcnt > 0 &&
+                       new_rate < old_rate)
+               ret = tegra_dvfs_set_rate(c, new_rate);
 
-       return 0;
-}
-
-int clk_set_parent(struct clk *c, struct clk *parent)
-{
-       int ret;
-       unsigned long flags;
-       spin_lock_irqsave(&clock_lock, flags);
-       ret = clk_set_parent_locked(c, parent);
-       spin_unlock_irqrestore(&clock_lock, flags);
+out:
+       clk_unlock_restore(c, &flags);
        return ret;
 }
 EXPORT_SYMBOL(clk_set_parent);
@@ -327,92 +313,112 @@ EXPORT_SYMBOL(clk_get_parent);
 
 int clk_set_rate_locked(struct clk *c, unsigned long rate)
 {
-       int ret;
-
-       if (rate > c->max_rate)
-               rate = c->max_rate;
-
-       if (!c->ops || !c->ops->set_rate)
-               return -ENOSYS;
-
-       ret = c->ops->set_rate(c, rate);
-
-       if (ret)
-               return ret;
-
-       clk_recalculate_rate(c);
+       int ret = 0;
+       unsigned long old_rate, max_rate;
+       long new_rate;
 
-       propagate_rate(c);
+       old_rate = clk_get_rate_locked(c);
 
-       return 0;
-}
+       max_rate = c->max_rate;
+       if (c->ops && c->ops->get_max_rate)
+               max_rate = c->ops->get_max_rate(c);
+       if (rate > max_rate)
+               rate = max_rate;
 
-int clk_set_rate_cansleep(struct clk *c, unsigned long rate)
-{
-       int ret = 0;
-       unsigned long flags;
+       if (c->ops && c->ops->round_rate) {
+               new_rate = c->ops->round_rate(c, rate);
 
-       mutex_lock(&dvfs_lock);
+               if (new_rate < 0) {
+                       ret = new_rate;
+                       return ret;
+               }
 
-       if (rate > c->rate)
-               ret = dvfs_set_rate(c->dvfs, rate);
-       if (ret)
-               goto out;
+               rate = new_rate;
+       }
 
-       spin_lock_irqsave(&clock_lock, flags);
-       ret = clk_set_rate_locked(c, rate);
-       spin_unlock_irqrestore(&clock_lock, flags);
+       if (clk_is_auto_dvfs(c) && rate > old_rate && c->refcnt > 0) {
+               ret = tegra_dvfs_set_rate(c, rate);
+               if (ret)
+                       return ret;
+       }
 
+       ret = c->ops->set_rate(c, rate);
        if (ret)
-               goto out;
+               return ret;
 
-       ret = dvfs_set_rate(c->dvfs, rate);
+       if (clk_is_auto_dvfs(c) && rate < old_rate && c->refcnt > 0)
+               ret = tegra_dvfs_set_rate(c, rate);
 
-out:
-       mutex_unlock(&dvfs_lock);
        return ret;
 }
-EXPORT_SYMBOL(clk_set_rate_cansleep);
 
 int clk_set_rate(struct clk *c, unsigned long rate)
 {
-       int ret = 0;
        unsigned long flags;
+       int ret;
+
+       if (!c->ops || !c->ops->set_rate)
+               return -ENOSYS;
 
-       if (clk_is_dvfs(c))
-               BUG();
+       clk_lock_save(c, &flags);
 
-       spin_lock_irqsave(&clock_lock, flags);
        ret = clk_set_rate_locked(c, rate);
-       spin_unlock_irqrestore(&clock_lock, flags);
+
+       clk_unlock_restore(c, &flags);
 
        return ret;
 }
 EXPORT_SYMBOL(clk_set_rate);
 
-unsigned long clk_get_rate(struct clk *c)
+/* Must be called with clocks lock and all indvidual clock locks held */
+unsigned long clk_get_rate_all_locked(struct clk *c)
 {
-       unsigned long flags;
-       unsigned long ret;
-
-       spin_lock_irqsave(&clock_lock, flags);
+       u64 rate;
+       int mul = 1;
+       int div = 1;
+       struct clk *p = c;
+
+       while (p) {
+               c = p;
+               if (c->ops && c->ops->recalculate_rate)
+                       c->ops->recalculate_rate(c);
+               if (c->mul != 0 && c->div != 0) {
+                       mul *= c->mul;
+                       div *= c->div;
+               }
+               p = c->parent;
+       }
 
-       ret = c->rate;
+       rate = c->rate;
+       rate *= mul;
+       do_div(rate, div);
 
-       spin_unlock_irqrestore(&clock_lock, flags);
-       return ret;
+       return rate;
 }
-EXPORT_SYMBOL(clk_get_rate);
 
 long clk_round_rate(struct clk *c, unsigned long rate)
 {
-       if (!c->ops || !c->ops->round_rate)
-               return -ENOSYS;
+       unsigned long flags, max_rate;
+       long ret;
 
-       if (rate > c->max_rate)
-               rate = c->max_rate;
+       clk_lock_save(c, &flags);
 
-       return c->ops->round_rate(c, rate);
+       if (!c->ops || !c->ops->round_rate) {
+               ret = -ENOSYS;
+               goto out;
+       }
+
+       max_rate = c->max_rate;
+       if (c->ops && c->ops->get_max_rate)
+               max_rate = c->ops->get_max_rate(c);
+       if (rate > max_rate)
+               rate = max_rate;
+
+       ret = c->ops->round_rate(c, rate);
+
+out:
+       clk_unlock_restore(c, &flags);
+       return ret;
 }
 EXPORT_SYMBOL(clk_round_rate);
 
@@ -479,48 +485,215 @@ EXPORT_SYMBOL(tegra_clk_init_from_table);
 
 void tegra_periph_reset_deassert(struct clk *c)
 {
-       tegra2_periph_reset_deassert(c);
+       BUG_ON(!c->ops->reset);
+       c->ops->reset(c, false);
 }
 EXPORT_SYMBOL(tegra_periph_reset_deassert);
 
 void tegra_periph_reset_assert(struct clk *c)
 {
-       tegra2_periph_reset_assert(c);
+       BUG_ON(!c->ops->reset);
+       c->ops->reset(c, true);
 }
 EXPORT_SYMBOL(tegra_periph_reset_assert);
 
 void __init tegra_init_clock(void)
 {
-       tegra2_init_clocks();
+       tegra_soc_init_clocks();
+       tegra_soc_init_dvfs();
 }
 
-int __init tegra_init_dvfs(void)
+/*
+ * The SDMMC controllers have extra bits in the clock source register that
+ * adjust the delay between the clock and data to compenstate for delays
+ * on the PCB.
+ */
+void tegra_sdmmc_tap_delay(struct clk *c, int delay)
 {
-       struct clk *c, *safe;
+       unsigned long flags;
 
-       mutex_lock(&dvfs_lock);
+       clk_lock_save(c, &flags);
+       tegra2_sdmmc_tap_delay(c, delay);
+       clk_unlock_restore(c, &flags);
+}
 
-       list_for_each_entry_safe(c, safe, &clocks, node)
-               if (c->dvfs)
-                       dvfs_init(c);
+static bool tegra_keep_boot_clocks = false;
+static int __init tegra_keep_boot_clocks_setup(char *__unused)
+{
+       tegra_keep_boot_clocks = true;
+       return 1;
+}
+__setup("tegra_keep_boot_clocks", tegra_keep_boot_clocks_setup);
+
+/*
+ * Iterate through all clocks, disabling any for which the refcount is 0
+ * but the clock init detected the bootloader left the clock on.
+ */
+static int __init tegra_init_disable_boot_clocks(void)
+{
+       unsigned long flags;
+       struct clk *c;
 
-       mutex_unlock(&dvfs_lock);
+       mutex_lock(&clock_list_lock);
 
+       list_for_each_entry(c, &clocks, node) {
+               clk_lock_save(c, &flags);
+               if (c->refcnt == 0 && c->state == ON &&
+                               c->ops && c->ops->disable) {
+                       pr_warn_once("%s clocks left on by bootloader:\n",
+                               tegra_keep_boot_clocks ?
+                                       "Prevented disabling" :
+                                       "Disabling");
+
+                       pr_warn("   %s\n", c->name);
+
+                       if (!tegra_keep_boot_clocks) {
+                               c->ops->disable(c);
+                               c->state = OFF;
+                       }
+               }
+               clk_unlock_restore(c, &flags);
+       }
+
+       mutex_unlock(&clock_list_lock);
        return 0;
 }
-
-late_initcall(tegra_init_dvfs);
+late_initcall(tegra_init_disable_boot_clocks);
 
 #ifdef CONFIG_DEBUG_FS
+
+/*
+ * Attempt to lock all the clocks that are marked cansleep
+ * Must be called with irqs enabled
+ */
+static int __clk_lock_all_mutexes(void)
+{
+       struct clk *c;
+
+       might_sleep();
+
+       list_for_each_entry(c, &clocks, node)
+               if (clk_cansleep(c))
+                       if (!mutex_trylock(&c->mutex))
+                               goto unlock_mutexes;
+
+       return 0;
+
+unlock_mutexes:
+       list_for_each_entry_continue_reverse(c, &clocks, node)
+               if (clk_cansleep(c))
+                       mutex_unlock(&c->mutex);
+
+       return -EAGAIN;
+}
+
+/*
+ * Attempt to lock all the clocks that are not marked cansleep
+ * Must be called with irqs disabled
+ */
+static int __clk_lock_all_spinlocks(void)
+{
+       struct clk *c;
+
+       list_for_each_entry(c, &clocks, node)
+               if (!clk_cansleep(c))
+                       if (!spin_trylock(&c->spinlock))
+                               goto unlock_spinlocks;
+
+       return 0;
+
+unlock_spinlocks:
+       list_for_each_entry_continue_reverse(c, &clocks, node)
+               if (!clk_cansleep(c))
+                       spin_unlock(&c->spinlock);
+
+       return -EAGAIN;
+}
+
+static void __clk_unlock_all_mutexes(void)
+{
+       struct clk *c;
+
+       list_for_each_entry_reverse(c, &clocks, node)
+               if (clk_cansleep(c))
+                       mutex_unlock(&c->mutex);
+}
+
+static void __clk_unlock_all_spinlocks(void)
+{
+       struct clk *c;
+
+       list_for_each_entry_reverse(c, &clocks, node)
+               if (!clk_cansleep(c))
+                       spin_unlock(&c->spinlock);
+}
+
+/*
+ * This function retries until it can take all locks, and may take
+ * an arbitrarily long time to complete.
+ * Must be called with irqs enabled, returns with irqs disabled
+ * Must be called with clock_list_lock held
+ */
+static void clk_lock_all(void)
+{
+       int ret;
+retry:
+       ret = __clk_lock_all_mutexes();
+       if (ret)
+               goto failed_mutexes;
+
+       local_irq_disable();
+
+       ret = __clk_lock_all_spinlocks();
+       if (ret)
+               goto failed_spinlocks;
+
+       /* All locks taken successfully, return */
+       return;
+
+failed_spinlocks:
+       local_irq_enable();
+       __clk_unlock_all_mutexes();
+failed_mutexes:
+       msleep(1);
+       goto retry;
+}
+
+/*
+ * Unlocks all clocks after a clk_lock_all
+ * Must be called with irqs disabled, returns with irqs enabled
+ * Must be called with clock_list_lock held
+ */
+static void clk_unlock_all(void)
+{
+       __clk_unlock_all_spinlocks();
+
+       local_irq_enable();
+
+       __clk_unlock_all_mutexes();
+}
+
 static struct dentry *clk_debugfs_root;
 
+static void dvfs_show_one(struct seq_file *s, struct dvfs *d, int level)
+{
+       seq_printf(s, "%*s  %-*s%21s%d mV\n",
+                       level * 3 + 1, "",
+                       30 - level * 3, d->dvfs_rail->reg_id,
+                       "",
+                       d->cur_millivolts);
+}
 
 static void clock_tree_show_one(struct seq_file *s, struct clk *c, int level)
 {
        struct clk *child;
-       struct clk *safe;
        const char *state = "uninit";
        char div[8] = {0};
+       unsigned long rate = clk_get_rate_all_locked(c);
+       unsigned long max_rate = c->max_rate;
+
+       if (c->ops && c->ops->get_max_rate)
+               max_rate = c->ops->get_max_rate(c);
 
        if (c->state == ON)
                state = "on";
@@ -546,11 +719,18 @@ static void clock_tree_show_one(struct seq_file *s, struct clk *c, int level)
 
        seq_printf(s, "%*s%c%c%-*s %-6s %-3d %-8s %-10lu\n",
                level * 3 + 1, "",
-               c->rate > c->max_rate ? '!' : ' ',
+               rate > max_rate ? '!' : ' ',
                !c->set ? '*' : ' ',
                30 - level * 3, c->name,
-               state, c->refcnt, div, c->rate);
-       list_for_each_entry_safe(child, safe, &c->children, sibling) {
+               state, c->refcnt, div, rate);
+
+       if (c->dvfs)
+               dvfs_show_one(s, c->dvfs, level + 1);
+
+       list_for_each_entry(child, &clocks, node) {
+               if (child->parent != c)
+                       continue;
+
                clock_tree_show_one(s, child, level + 1);
        }
 }
@@ -558,14 +738,20 @@ static void clock_tree_show_one(struct seq_file *s, struct clk *c, int level)
 static int clock_tree_show(struct seq_file *s, void *data)
 {
        struct clk *c;
-       unsigned long flags;
        seq_printf(s, "   clock                          state  ref div      rate\n");
        seq_printf(s, "--------------------------------------------------------------\n");
-       spin_lock_irqsave(&clock_lock, flags);
+
+       mutex_lock(&clock_list_lock);
+
+       clk_lock_all();
+
        list_for_each_entry(c, &clocks, node)
                if (c->parent == NULL)
                        clock_tree_show_one(s, c, 0);
-       spin_unlock_irqrestore(&clock_lock, flags);
+
+       clk_unlock_all();
+
+       mutex_unlock(&clock_list_lock);
        return 0;
 }
 
@@ -606,9 +792,91 @@ static const struct file_operations possible_parents_fops = {
        .release        = single_release,
 };
 
+static int parent_show(struct seq_file *s, void *data)
+{
+       struct clk *c = s->private;
+       struct clk *p = clk_get_parent(c);
+
+       seq_printf(s, "%s\n", p ? p->name : "clk_root");
+       return 0;
+}
+
+static int parent_open(struct inode *inode, struct file *file)
+{
+       return single_open(file, parent_show, inode->i_private);
+}
+
+static int rate_get(void *data, u64 *val)
+{
+       struct clk *c = (struct clk *)data;
+       *val = (u64)clk_get_rate(c);
+       return 0;
+}
+
+#ifdef CONFIG_TEGRA_CLOCK_DEBUG_WRITE
+
+static const mode_t parent_rate_mode =  S_IRUGO | S_IWUGO;
+
+static ssize_t parent_write(struct file *file,
+       const char __user *userbuf, size_t count, loff_t *ppos)
+{
+       struct seq_file *s = file->private_data;
+       struct clk *c = s->private;
+       struct clk *p = NULL;
+       char buf[32];
+
+       if (sizeof(buf) <= count)
+               return -EINVAL;
+
+       if (copy_from_user(buf, userbuf, count))
+               return -EFAULT;
+
+       /* terminate buffer and trim - white spaces may be appended
+        *  at the end when invoked from shell command line */
+       buf[count]='\0';
+       strim(buf);
+
+       p = tegra_get_clock_by_name(buf);
+       if (!p)
+               return -EINVAL;
+
+       clk_set_parent(c, p);
+       return count;
+}
+
+static const struct file_operations parent_fops = {
+       .open           = parent_open,
+       .read           = seq_read,
+       .write          = parent_write,
+       .llseek         = seq_lseek,
+       .release        = single_release,
+};
+
+static int rate_set(void *data, u64 val)
+{
+       struct clk *c = (struct clk *)data;
+       clk_set_rate(c, (unsigned long)val);
+       return 0;
+}
+DEFINE_SIMPLE_ATTRIBUTE(rate_fops, rate_get, rate_set, "%llu\n");
+
+#else
+
+static const mode_t parent_rate_mode =  S_IRUGO;
+
+static const struct file_operations parent_fops = {
+       .open           = parent_open,
+       .read           = seq_read,
+       .llseek         = seq_lseek,
+       .release        = single_release,
+};
+
+DEFINE_SIMPLE_ATTRIBUTE(rate_fops, rate_get, NULL, "%llu\n");
+#endif
+
 static int clk_debugfs_register_one(struct clk *c)
 {
-       struct dentry *d, *child, *child_tmp;
+       struct dentry *d;
 
        d = debugfs_create_dir(c->name, clk_debugfs_root);
        if (!d)
@@ -619,11 +887,21 @@ static int clk_debugfs_register_one(struct clk *c)
        if (!d)
                goto err_out;
 
-       d = debugfs_create_u32("rate", S_IRUGO, c->dent, (u32 *)&c->rate);
+       d = debugfs_create_x32("flags", S_IRUGO, c->dent, (u32 *)&c->flags);
        if (!d)
                goto err_out;
 
-       d = debugfs_create_x32("flags", S_IRUGO, c->dent, (u32 *)&c->flags);
+       d = debugfs_create_u32("max", S_IRUGO, c->dent, (u32 *)&c->max_rate);
+       if (!d)
+               goto err_out;
+
+       d = debugfs_create_file(
+               "parent", parent_rate_mode, c->dent, c, &parent_fops);
+       if (!d)
+               goto err_out;
+
+       d = debugfs_create_file(
+               "rate", parent_rate_mode, c->dent, c, &rate_fops);
        if (!d)
                goto err_out;
 
@@ -637,10 +915,7 @@ static int clk_debugfs_register_one(struct clk *c)
        return 0;
 
 err_out:
-       d = c->dent;
-       list_for_each_entry_safe(child, child_tmp, &d->d_subdirs, d_u.d_child)
-               debugfs_remove(child);
-       debugfs_remove(c->dent);
+       debugfs_remove_recursive(c->dent);
        return -ENOMEM;
 }
 
@@ -679,6 +954,9 @@ static int __init clk_debugfs_init(void)
        if (!d)
                goto err_out;
 
+       if (dvfs_debugfs_init(clk_debugfs_root))
+               goto err_out;
+
        list_for_each_entry(c, &clocks, node) {
                err = clk_debugfs_register(c);
                if (err)