]> Pileus Git - ~andy/linux/blobdiff - arch/sh/kernel/cpu/clock.c
sh: clkfwk: refactor rate propagation.
[~andy/linux] / arch / sh / kernel / cpu / clock.c
index 1dc896483b591169bcf1254b70e4961ed3e88337..0a06df8cde2b58ace6b741ba3f917083b468374e 100644 (file)
@@ -1,11 +1,11 @@
 /*
  * arch/sh/kernel/cpu/clock.c - SuperH clock framework
  *
- *  Copyright (C) 2005, 2006, 2007  Paul Mundt
+ *  Copyright (C) 2005 - 2009  Paul Mundt
  *
  * This clock framework is derived from the OMAP version by:
  *
- *     Copyright (C) 2004 - 2005 Nokia Corporation
+ *     Copyright (C) 2004 - 2008 Nokia Corporation
  *     Written by Tuukka Tikkanen <tuukka.tikkanen@elektrobit.com>
  *
  *  Modified for omap shared clock framework by Tony Lindgren <tony@atomide.com>
@@ -19,7 +19,6 @@
 #include <linux/module.h>
 #include <linux/mutex.h>
 #include <linux/list.h>
-#include <linux/kref.h>
 #include <linux/kobject.h>
 #include <linux/sysdev.h>
 #include <linux/seq_file.h>
@@ -44,20 +43,20 @@ static DEFINE_MUTEX(clock_list_sem);
  */
 static struct clk master_clk = {
        .name           = "master_clk",
-       .flags          = CLK_ALWAYS_ENABLED | CLK_RATE_PROPAGATES,
+       .flags          = CLK_ALWAYS_ENABLED,
        .rate           = CONFIG_SH_PCLK_FREQ,
 };
 
 static struct clk module_clk = {
        .name           = "module_clk",
        .parent         = &master_clk,
-       .flags          = CLK_ALWAYS_ENABLED | CLK_RATE_PROPAGATES,
+       .flags          = CLK_ALWAYS_ENABLED,
 };
 
 static struct clk bus_clk = {
        .name           = "bus_clk",
        .parent         = &master_clk,
-       .flags          = CLK_ALWAYS_ENABLED | CLK_RATE_PROPAGATES,
+       .flags          = CLK_ALWAYS_ENABLED,
 };
 
 static struct clk cpu_clk = {
@@ -76,21 +75,25 @@ static struct clk *onchip_clocks[] = {
        &cpu_clk,
 };
 
-static void propagate_rate(struct clk *clk)
+/* Used for clocks that always have same value as the parent clock */
+unsigned long followparent_recalc(struct clk *clk)
+{
+       return clk->parent->rate;
+}
+
+/* Propagate rate to children */
+void propagate_rate(struct clk *tclk)
 {
        struct clk *clkp;
 
-       list_for_each_entry(clkp, &clock_list, node) {
-               if (likely(clkp->parent != clk))
-                       continue;
-               if (likely(clkp->ops && clkp->ops->recalc))
-                       clkp->ops->recalc(clkp);
-               if (unlikely(clkp->flags & CLK_RATE_PROPAGATES))
-                       propagate_rate(clkp);
+       list_for_each_entry(clkp, &tclk->children, sibling) {
+               if (clkp->ops->recalc)
+                       clkp->rate = clkp->ops->recalc(clkp);
+               propagate_rate(clkp);
        }
 }
 
-static int __clk_enable(struct clk *clk)
+static void __clk_init(struct clk *clk)
 {
        /*
         * See if this is the first time we're enabling the clock, some
@@ -99,17 +102,34 @@ static int __clk_enable(struct clk *clk)
         * changes and the clock needs to hunt for the proper set of
         * divisors to use before it can effectively recalc.
         */
-       if (unlikely(atomic_read(&clk->kref.refcount) == 1))
+
+       if (clk->flags & CLK_NEEDS_INIT) {
                if (clk->ops && clk->ops->init)
                        clk->ops->init(clk);
 
-       kref_get(&clk->kref);
+               clk->flags &= ~CLK_NEEDS_INIT;
+       }
+}
+
+static int __clk_enable(struct clk *clk)
+{
+       if (!clk)
+               return -EINVAL;
+
+       clk->usecount++;
 
+       /* nothing to do if always enabled */
        if (clk->flags & CLK_ALWAYS_ENABLED)
                return 0;
 
-       if (likely(clk->ops && clk->ops->enable))
-               clk->ops->enable(clk);
+       if (clk->usecount == 1) {
+               __clk_init(clk);
+
+               __clk_enable(clk->parent);
+
+               if (clk->ops && clk->ops->enable)
+                       clk->ops->enable(clk);
+       }
 
        return 0;
 }
@@ -119,11 +139,6 @@ int clk_enable(struct clk *clk)
        unsigned long flags;
        int ret;
 
-       if (!clk)
-               return -EINVAL;
-
-       clk_enable(clk->parent);
-
        spin_lock_irqsave(&clock_lock, flags);
        ret = __clk_enable(clk);
        spin_unlock_irqrestore(&clock_lock, flags);
@@ -132,21 +147,23 @@ int clk_enable(struct clk *clk)
 }
 EXPORT_SYMBOL_GPL(clk_enable);
 
-static void clk_kref_release(struct kref *kref)
-{
-       /* Nothing to do */
-}
-
 static void __clk_disable(struct clk *clk)
 {
-       int count = kref_put(&clk->kref, clk_kref_release);
+       if (!clk)
+               return;
+
+       clk->usecount--;
+
+       WARN_ON(clk->usecount < 0);
 
        if (clk->flags & CLK_ALWAYS_ENABLED)
                return;
 
-       if (!count) {   /* count reaches zero, disable the clock */
+       if (clk->usecount == 0) {
                if (likely(clk->ops && clk->ops->disable))
                        clk->ops->disable(clk);
+
+               __clk_disable(clk->parent);
        }
 }
 
@@ -154,30 +171,61 @@ void clk_disable(struct clk *clk)
 {
        unsigned long flags;
 
-       if (!clk)
-               return;
-
        spin_lock_irqsave(&clock_lock, flags);
        __clk_disable(clk);
        spin_unlock_irqrestore(&clock_lock, flags);
-
-       clk_disable(clk->parent);
 }
 EXPORT_SYMBOL_GPL(clk_disable);
 
+static LIST_HEAD(root_clks);
+
+/**
+ * recalculate_root_clocks - recalculate and propagate all root clocks
+ *
+ * Recalculates all root clocks (clocks with no parent), which if the
+ * clock's .recalc is set correctly, should also propagate their rates.
+ * Called at init.
+ */
+void recalculate_root_clocks(void)
+{
+       struct clk *clkp;
+
+       list_for_each_entry(clkp, &root_clks, sibling) {
+               if (clkp->ops->recalc)
+                       clkp->rate = clkp->ops->recalc(clkp);
+               propagate_rate(clkp);
+       }
+}
+
 int clk_register(struct clk *clk)
 {
+       if (clk == NULL || IS_ERR(clk))
+               return -EINVAL;
+
+       /*
+        * trap out already registered clocks
+        */
+       if (clk->node.next || clk->node.prev)
+               return 0;
+
        mutex_lock(&clock_list_sem);
 
+       INIT_LIST_HEAD(&clk->children);
+
+       if (clk->parent)
+               list_add(&clk->sibling, &clk->parent->children);
+       else
+               list_add(&clk->sibling, &root_clks);
+
        list_add(&clk->node, &clock_list);
-       kref_init(&clk->kref);
+       clk->usecount = 0;
+       clk->flags |= CLK_NEEDS_INIT;
 
        mutex_unlock(&clock_list_sem);
 
        if (clk->flags & CLK_ALWAYS_ENABLED) {
+               __clk_init(clk);
                pr_debug( "Clock '%s' is ALWAYS_ENABLED\n", clk->name);
-               if (clk->ops && clk->ops->init)
-                       clk->ops->init(clk);
                if (clk->ops && clk->ops->enable)
                        clk->ops->enable(clk);
                pr_debug( "Enabled.");
@@ -190,6 +238,7 @@ EXPORT_SYMBOL_GPL(clk_register);
 void clk_unregister(struct clk *clk)
 {
        mutex_lock(&clock_list_sem);
+       list_del(&clk->sibling);
        list_del(&clk->node);
        mutex_unlock(&clock_list_sem);
 }
@@ -216,50 +265,53 @@ int clk_set_rate_ex(struct clk *clk, unsigned long rate, int algo_id)
 
                spin_lock_irqsave(&clock_lock, flags);
                ret = clk->ops->set_rate(clk, rate, algo_id);
+               if (ret == 0) {
+                       if (clk->ops->recalc)
+                               clk->rate = clk->ops->recalc(clk);
+                       propagate_rate(clk);
+               }
                spin_unlock_irqrestore(&clock_lock, flags);
        }
 
-       if (unlikely(clk->flags & CLK_RATE_PROPAGATES))
-               propagate_rate(clk);
-
        return ret;
 }
 EXPORT_SYMBOL_GPL(clk_set_rate_ex);
 
 void clk_recalc_rate(struct clk *clk)
 {
-       if (likely(clk->ops && clk->ops->recalc)) {
-               unsigned long flags;
+       unsigned long flags;
 
-               spin_lock_irqsave(&clock_lock, flags);
-               clk->ops->recalc(clk);
-               spin_unlock_irqrestore(&clock_lock, flags);
-       }
+       if (!clk->ops->recalc)
+               return;
 
-       if (unlikely(clk->flags & CLK_RATE_PROPAGATES))
-               propagate_rate(clk);
+       spin_lock_irqsave(&clock_lock, flags);
+       clk->rate = clk->ops->recalc(clk);
+       propagate_rate(clk);
+       spin_unlock_irqrestore(&clock_lock, flags);
 }
 EXPORT_SYMBOL_GPL(clk_recalc_rate);
 
 int clk_set_parent(struct clk *clk, struct clk *parent)
 {
+       unsigned long flags;
        int ret = -EINVAL;
-       struct clk *old;
 
        if (!parent || !clk)
                return ret;
 
-       old = clk->parent;
-       if (likely(clk->ops && clk->ops->set_parent)) {
-               unsigned long flags;
-               spin_lock_irqsave(&clock_lock, flags);
-               ret = clk->ops->set_parent(clk, parent);
-               spin_unlock_irqrestore(&clock_lock, flags);
-               clk->parent = (ret ? old : parent);
-       }
+       spin_lock_irqsave(&clock_lock, flags);
+       if (clk->usecount == 0) {
+               if (clk->ops->set_parent)
+                       ret = clk->ops->set_parent(clk, parent);
+               if (ret == 0) {
+                       if (clk->ops->recalc)
+                               clk->rate = clk->ops->recalc(clk);
+                       propagate_rate(clk);
+               }
+       } else
+               ret = -EBUSY;
+       spin_unlock_irqrestore(&clock_lock, flags);
 
-       if (unlikely(clk->flags & CLK_RATE_PROPAGATES))
-               propagate_rate(clk);
        return ret;
 }
 EXPORT_SYMBOL_GPL(clk_set_parent);
@@ -353,7 +405,7 @@ static int show_clocks(char *buf, char **start, off_t off,
                p += sprintf(p, "%-12s\t: %ld.%02ldMHz\t%s\n", clk->name,
                             rate / 1000000, (rate % 1000000) / 10000,
                             ((clk->flags & CLK_ALWAYS_ENABLED) ||
-                             (atomic_read(&clk->kref.refcount) != 1)) ?
+                             clk->usecount > 0) ?
                             "enabled" : "disabled");
        }
 
@@ -369,20 +421,22 @@ static int clks_sysdev_suspend(struct sys_device *dev, pm_message_t state)
        switch (state.event) {
        case PM_EVENT_ON:
                /* Resumeing from hibernation */
-               if (prev_state.event == PM_EVENT_FREEZE) {
-                       list_for_each_entry(clkp, &clock_list, node)
-                               if (likely(clkp->ops)) {
-                                       unsigned long rate = clkp->rate;
-
-                                       if (likely(clkp->ops->set_parent))
-                                               clkp->ops->set_parent(clkp,
-                                                       clkp->parent);
-                                       if (likely(clkp->ops->set_rate))
-                                               clkp->ops->set_rate(clkp,
-                                                       rate, NO_CHANGE);
-                                       else if (likely(clkp->ops->recalc))
-                                               clkp->ops->recalc(clkp);
-                                       }
+               if (prev_state.event != PM_EVENT_FREEZE)
+                       break;
+
+               list_for_each_entry(clkp, &clock_list, node) {
+                       if (likely(clkp->ops)) {
+                               unsigned long rate = clkp->rate;
+
+                               if (likely(clkp->ops->set_parent))
+                                       clkp->ops->set_parent(clkp,
+                                               clkp->parent);
+                               if (likely(clkp->ops->set_rate))
+                                       clkp->ops->set_rate(clkp,
+                                               rate, NO_CHANGE);
+                               else if (likely(clkp->ops->recalc))
+                                       clkp->rate = clkp->ops->recalc(clkp);
+                       }
                }
                break;
        case PM_EVENT_FREEZE:
@@ -440,8 +494,7 @@ int __init clk_init(void)
        ret |= arch_clk_init();
 
        /* Kick the child clocks.. */
-       propagate_rate(&master_clk);
-       propagate_rate(&bus_clk);
+       recalculate_root_clocks();
 
        return ret;
 }