From 3d89a9035995123449bd159d7cca79df42fff61d Mon Sep 17 00:00:00 2001 From: Colin Cross Date: Mon, 1 Nov 2010 17:17:47 -0700 Subject: [PATCH 1/8] ARM: tegra: dvfs: Disable dvfs on sdmmc The sdhci core calls clock functions with a spinlock held, which conflicts with the mutex in dvfs clocks. Disable dvfs on sdmmc for now. Signed-off-by: Colin Cross Change-Id: I59c0648ab6718571794e082d1f4828f158d00031 --- arch/arm/mach-tegra/tegra2_dvfs.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/arch/arm/mach-tegra/tegra2_dvfs.c b/arch/arm/mach-tegra/tegra2_dvfs.c index 4f56fb88fc17..024e1b970638 100644 --- a/arch/arm/mach-tegra/tegra2_dvfs.c +++ b/arch/arm/mach-tegra/tegra2_dvfs.c @@ -81,10 +81,20 @@ static struct dvfs dvfs_init[] = { CPU_DVFS("cpu", 3, MHZ, 730, 760, 845, 845, 1000), /* Core voltages (mV): 950, 1000, 1100, 1200, 1275 */ + +#if 0 + /* + * The sdhci core calls the clock ops with a spinlock held, which + * conflicts with the sleeping dvfs api. + * For now, boards must ensure that the core voltage does not drop + * below 1V, or that the sdmmc busses are set to 44 MHz or less. + */ CORE_DVFS("sdmmc1", 1, KHZ, 44000, 52000, 52000, 52000, 52000), CORE_DVFS("sdmmc2", 1, KHZ, 44000, 52000, 52000, 52000, 52000), CORE_DVFS("sdmmc3", 1, KHZ, 44000, 52000, 52000, 52000, 52000), CORE_DVFS("sdmmc4", 1, KHZ, 44000, 52000, 52000, 52000, 52000), +#endif + CORE_DVFS("ndflash", 1, KHZ, 130000, 150000, 158000, 164000, 164000), CORE_DVFS("nor", 1, KHZ, 0, 92000, 92000, 92000, 92000), CORE_DVFS("ide", 1, KHZ, 0, 0, 100000, 100000, 100000), From 1f8ca000a05c44e338438c729022d238b167fba4 Mon Sep 17 00:00:00 2001 From: Colin Cross Date: Mon, 1 Nov 2010 17:24:32 -0700 Subject: [PATCH 2/8] ARM: tegra: clock: Drop set_rate on audio clocks Signed-off-by: Colin Cross Change-Id: Idabc71386e7a2fba214d243405e70149faf47a8e --- arch/arm/mach-tegra/tegra2_clocks.c | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/arch/arm/mach-tegra/tegra2_clocks.c b/arch/arm/mach-tegra/tegra2_clocks.c index 253de093a8ef..7f5ed03fa958 100644 --- a/arch/arm/mach-tegra/tegra2_clocks.c +++ b/arch/arm/mach-tegra/tegra2_clocks.c @@ -1083,30 +1083,10 @@ static int tegra2_audio_sync_clk_set_parent(struct clk *c, struct clk *p) return -EINVAL; } -static int tegra2_audio_sync_clk_set_rate(struct clk *c, unsigned long rate) -{ - unsigned long parent_rate; - if (!c->parent) { - pr_err("%s: clock has no parent\n", __func__); - return -EINVAL; - } - parent_rate = c->parent->rate; - if (rate != parent_rate) { - pr_err("%s: %s/%ld differs from parent %s/%ld\n", - __func__, - c->name, rate, - c->parent->name, parent_rate); - return -EINVAL; - } - c->rate = parent_rate; - return 0; -} - static struct clk_ops tegra_audio_sync_clk_ops = { .init = tegra2_audio_sync_clk_init, .enable = tegra2_audio_sync_clk_enable, .disable = tegra2_audio_sync_clk_disable, - .set_rate = tegra2_audio_sync_clk_set_rate, .set_parent = tegra2_audio_sync_clk_set_parent, }; From c95714d0ba915e422ad676f68630fcd513f34295 Mon Sep 17 00:00:00 2001 From: Colin Cross Date: Mon, 1 Nov 2010 17:25:30 -0700 Subject: [PATCH 3/8] ARM: tegra: clock: Redo clock locking Give each clock its own lock, and remove all lock traversals from parent to child clocks to prevent AB-BA deadlocks. Signed-off-by: Colin Cross Change-Id: I0afb7d1bca956439b1a4f17bbc6748aaec706b49 --- arch/arm/mach-tegra/clock.c | 628 ++++++++++++++++--------- arch/arm/mach-tegra/clock.h | 16 +- arch/arm/mach-tegra/include/mach/clk.h | 1 + arch/arm/mach-tegra/tegra2_clocks.c | 75 +-- 4 files changed, 461 insertions(+), 259 deletions(-) diff --git a/arch/arm/mach-tegra/clock.c b/arch/arm/mach-tegra/clock.c index d34f0efbdd37..4486214c52f5 100644 --- a/arch/arm/mach-tegra/clock.c +++ b/arch/arm/mach-tegra/clock.c @@ -18,119 +18,205 @@ #include #include -#include -#include -#include #include -#include +#include +#include +#include +#include #include +#include + #include + #include #include "board.h" #include "clock.h" #include "dvfs.h" -static LIST_HEAD(clocks); - /* - * clock_lock must be held when: - * Accessing any clock register non-atomically - * or - * Relying on any state of a clk struct not to change, unless clk_is_dvfs - * returns true on that clk struct, and dvfs_lock is held instead. + * Locking: * - * Any function that changes the state of a clk struct must hold - * the dvfs_lock if clk_is_auto_dvfs(clk) is true, and the clock_lock. + * 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. * - * When taking dvfs_lock and clock_lock, dvfs_lock must be taken first. + * 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. 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 lock, 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_SPINLOCK(clock_lock); +static DEFINE_MUTEX(clock_list_lock); +static LIST_HEAD(clocks); static inline bool clk_is_auto_dvfs(struct clk *c) { - smp_rmb(); return c->auto_dvfs; -}; +} static inline bool clk_is_dvfs(struct clk *c) { - smp_rmb(); return c->is_dvfs; -}; +} + +static inline bool clk_cansleep(struct clk *c) +{ + return c->cansleep; +} + +#define clk_lock_save(c, flags) \ + do { \ + if (clk_cansleep(c)) { \ + flags = 0; \ + mutex_lock(&c->mutex); \ + } else { \ + spin_lock_irqsave(&c->spinlock, flags); \ + } \ + } while (0) + +#define clk_unlock_restore(c, flags) \ + do { \ + if (clk_cansleep(c)) \ + mutex_unlock(&c->mutex); \ + else \ + spin_unlock_irqrestore(&c->spinlock, flags); \ + } while (0) + +static inline void clk_lock_init(struct clk *c) +{ + mutex_init(&c->mutex); + spin_lock_init(&c->spinlock); +} struct clk *tegra_get_clock_by_name(const char *name) { struct clk *c; struct clk *ret = NULL; - unsigned long flags; - spin_lock_irqsave(&clock_lock, flags); + mutex_lock(&clock_list_lock); list_for_each_entry(c, &clocks, node) { if (strcmp(c->name, name) == 0) { ret = c; break; } } - spin_unlock_irqrestore(&clock_lock, flags); + mutex_unlock(&clock_list_lock); return ret; } +/* Must be called with clk_lock(c) held */ static unsigned long clk_predict_rate_from_parent(struct clk *c, struct clk *p) { u64 rate; - rate = p->rate; + rate = clk_get_rate(p); if (c->mul != 0 && c->div != 0) { - rate = rate * c->mul; + rate *= c->mul; do_div(rate, c->div); } return rate; } -static void clk_recalculate_rate(struct clk *c) +/* Must be called with clk_lock(c) held */ +unsigned long clk_get_rate_locked(struct clk *c) { unsigned long rate; - if (!c->parent) - return; + if (c->parent) + rate = clk_predict_rate_from_parent(c, c->parent); + else + rate = c->rate; - rate = clk_predict_rate_from_parent(c, c->parent); + return rate; +} - if (rate > c->max_rate) - pr_warn("clocks: Set clock %s to rate %lu, max is %lu\n", - c->name, rate, c->max_rate); +unsigned long clk_get_rate(struct clk *c) +{ + unsigned long flags; + unsigned long rate; - c->rate = 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_set_cansleep(struct clk *c) +{ + struct clk *child; + BUG_ON(mutex_is_locked(&c->mutex)); + BUG_ON(spin_is_locked(&c->spinlock)); + + list_for_each_entry(child, &clocks, node) { + if (child->parent != c) + continue; + + 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); + + __clk_set_cansleep(child); + } + + c->cansleep = true; +} + +/* 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; + clk_lock_init(c); - spin_lock_irqsave(&clock_lock, flags); - - INIT_LIST_HEAD(&c->children); - INIT_LIST_HEAD(&c->sibling); INIT_LIST_HEAD(&c->dvfs); if (c->ops && c->ops->init) @@ -145,71 +231,58 @@ void clk_init(struct clk *c) c->state = ON; } - clk_recalculate_rate(c); - + 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; c->set = true; } } c->refcnt++; - - return 0; -} - -int clk_enable(struct clk *c) -{ - int ret; - unsigned long flags; - - if (clk_is_auto_dvfs(c)) { - lock_dvfs(); - ret = tegra_dvfs_set_rate(c, c->rate); - if (ret) - goto out; - } - - spin_lock_irqsave(&clock_lock, flags); - ret = clk_enable_locked(c); - spin_unlock_irqrestore(&clock_lock, flags); - out: - if (clk_is_auto_dvfs(c)) - unlock_dvfs(); - + clk_unlock_restore(c, flags); return ret; } EXPORT_SYMBOL(clk_enable); -void clk_disable_locked(struct clk *c) +void clk_disable(struct clk *c) { + unsigned long flags; + + clk_lock_save(c, flags); + 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) { @@ -217,79 +290,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(struct clk *c) -{ - unsigned long flags; + if (clk_is_auto_dvfs(c) && c->refcnt == 0) + tegra_dvfs_set_rate(c, 0); - if (clk_is_auto_dvfs(c)) - lock_dvfs(); - - spin_lock_irqsave(&clock_lock, flags); - clk_disable_locked(c); - spin_unlock_irqrestore(&clock_lock, flags); - - if (clk_is_auto_dvfs(c)) { - if (c->refcnt == 0) - tegra_dvfs_set_rate(c, 0); - unlock_dvfs(); - } + clk_unlock_restore(c, flags); } EXPORT_SYMBOL(clk_disable); -int clk_set_parent_locked(struct clk *c, struct clk *parent) -{ - int ret; - - if (!c->ops || !c->ops->set_parent) - return -ENOSYS; - - ret = c->ops->set_parent(c, parent); - - if (ret) - return ret; - - clk_recalculate_rate(c); - - propagate_rate(c); - - return 0; -} - int clk_set_parent(struct clk *c, struct clk *parent) { int ret = 0; unsigned long flags; - unsigned long new_rate = clk_predict_rate_from_parent(c, parent); + unsigned long new_rate; + unsigned long old_rate; + clk_lock_save(c, flags); - if (clk_is_auto_dvfs(c)) { - lock_dvfs(); - if (c->refcnt > 0 && (!c->parent || new_rate > c->rate)) - ret = tegra_dvfs_set_rate(c, new_rate); - if (!ret) + if (!c->ops || !c->ops->set_parent) { + ret = -ENOSYS; + goto out; + } + + new_rate = clk_predict_rate_from_parent(c, parent); + old_rate = clk_get_rate_locked(c); + + 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; } - spin_lock_irqsave(&clock_lock, flags); - ret = clk_set_parent_locked(c, parent); - spin_unlock_irqrestore(&clock_lock, flags); - if (!ret) + ret = c->ops->set_parent(c, parent); + if (ret) goto out; - if (clk_is_auto_dvfs(c) && c->refcnt > 0) + if (clk_is_auto_dvfs(c) && c->refcnt > 0 && + new_rate < old_rate) ret = tegra_dvfs_set_rate(c, new_rate); out: - if (clk_is_auto_dvfs(c)) - unlock_dvfs(); - + clk_unlock_restore(c, flags); return ret; } EXPORT_SYMBOL(clk_set_parent); @@ -300,86 +347,87 @@ struct clk *clk_get_parent(struct clk *c) } EXPORT_SYMBOL(clk_get_parent); -int clk_set_rate_locked(struct clk *c, unsigned long rate) -{ - int ret; - - if (rate == c->requested_rate) - return 0; - - if (rate > c->max_rate) - rate = c->max_rate; - - if (!c->ops || !c->ops->set_rate) - return -ENOSYS; - - c->requested_rate = rate; - - ret = c->ops->set_rate(c, rate); - - if (ret) - return ret; - - clk_recalculate_rate(c); - - propagate_rate(c); - - return 0; -} - int clk_set_rate(struct clk *c, unsigned long rate) { int ret = 0; unsigned long flags; + unsigned long old_rate; - if (clk_is_auto_dvfs(c)) { - lock_dvfs(); - if (rate > c->rate && c->refcnt > 0) - ret = tegra_dvfs_set_rate(c, rate); - if (ret) - goto out; + clk_lock_save(c, flags); + + if (!c->ops || !c->ops->set_rate) { + ret = -ENOSYS; + goto out; } - spin_lock_irqsave(&clock_lock, flags); - ret = clk_set_rate_locked(c, rate); - spin_unlock_irqrestore(&clock_lock, flags); - - if (ret) - goto out; - - if (clk_is_auto_dvfs(c) && c->refcnt > 0) - ret = tegra_dvfs_set_rate(c, rate); - -out: - if (clk_is_auto_dvfs(c)) - unlock_dvfs(); - return ret; -} -EXPORT_SYMBOL(clk_set_rate); - -unsigned long clk_get_rate(struct clk *c) -{ - unsigned long flags; - unsigned long ret; - - spin_lock_irqsave(&clock_lock, flags); - - ret = c->rate; - - spin_unlock_irqrestore(&clock_lock, flags); - return ret; -} -EXPORT_SYMBOL(clk_get_rate); - -long clk_round_rate(struct clk *c, unsigned long rate) -{ - if (!c->ops || !c->ops->round_rate) - return -ENOSYS; + old_rate = clk_get_rate_locked(c); if (rate > c->max_rate) rate = c->max_rate; - return c->ops->round_rate(c, rate); + if (clk_is_auto_dvfs(c) && rate > old_rate && c->refcnt > 0) { + ret = tegra_dvfs_set_rate(c, rate); + if (ret) + goto out; + } + + ret = c->ops->set_rate(c, rate); + if (ret) + goto out; + + if (clk_is_auto_dvfs(c) && rate < old_rate && c->refcnt > 0) + ret = tegra_dvfs_set_rate(c, rate); + +out: + clk_unlock_restore(c, flags); + return ret; +} +EXPORT_SYMBOL(clk_set_rate); + +/* Must be called with clocks lock and all indvidual clock locks held */ +unsigned long clk_get_rate_all_locked(struct clk *c) +{ + u64 rate; + int mul = 1; + int div = 1; + struct clk *p = c; + + while (p) { + c = p; + if (c->mul != 0 && c->div != 0) { + mul *= c->mul; + div *= c->div; + } + p = c->parent; + } + + rate = c->rate; + rate *= mul; + do_div(rate, div); + + return rate; +} + +long clk_round_rate(struct clk *c, unsigned long rate) +{ + unsigned long flags; + long ret; + + clk_lock_save(c, flags); + + if (!c->ops || !c->ops->round_rate) { + ret = -ENOSYS; + goto out; + } + + if (rate > c->max_rate) + rate = c->max_rate; + + ret = c->ops->round_rate(c, rate); + +out: + clk_unlock_restore(c, flags); + return ret; } EXPORT_SYMBOL(clk_round_rate); @@ -459,32 +507,51 @@ EXPORT_SYMBOL(tegra_periph_reset_assert); void __init tegra_init_clock(void) { tegra2_init_clocks(); + tegra2_init_dvfs(); } +/* + * Iterate through all clocks, setting the dvfs rate to the current clock + * rate on all auto dvfs clocks, and to the saved dvfs rate on all manual + * dvfs clocks. Used to enable dvfs during late init, after the regulators + * are available. + */ void __init tegra_clk_set_dvfs_rates(void) { + unsigned long flags; struct clk *c; + + mutex_lock(&clock_list_lock); + list_for_each_entry(c, &clocks, node) { + clk_lock_save(c, flags); if (clk_is_auto_dvfs(c)) { if (c->refcnt > 0) - tegra_dvfs_set_rate(c, c->rate); + tegra_dvfs_set_rate(c, clk_get_rate_locked(c)); else tegra_dvfs_set_rate(c, 0); } else if (clk_is_dvfs(c)) { tegra_dvfs_set_rate(c, c->dvfs_rate); } + clk_unlock_restore(c, flags); } + + mutex_unlock(&clock_list_lock); } +/* + * Iterate through all clocks, disabling any for which the refcount is 0 + * but the clock init detected the bootloader left the clock on. + */ int __init tegra_disable_boot_clocks(void) { unsigned long flags; struct clk *c; - lock_dvfs(); - spin_lock_irqsave(&clock_lock, flags); + 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_warning("Disabling clock %s left on by bootloader\n", @@ -492,15 +559,134 @@ int __init tegra_disable_boot_clocks(void) c->ops->disable(c); c->state = OFF; } + clk_unlock_restore(c, flags); } - spin_unlock_irqrestore(&clock_lock, flags); - unlock_dvfs(); + mutex_unlock(&clock_list_lock); return 0; } -late_initcall(tegra_disable_boot_clocks); + +int __init tegra_late_init_clock(void) +{ + tegra_disable_boot_clocks(); + tegra_clk_set_dvfs_rates(); + return 0; +} +late_initcall(tegra_late_init_clock); #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) @@ -515,7 +701,6 @@ static void dvfs_show_one(struct seq_file *s, struct dvfs *d, int level) static void clock_tree_show_one(struct seq_file *s, struct clk *c, int level) { struct clk *child; - struct clk *safe; struct dvfs *d; const char *state = "uninit"; char div[8] = {0}; @@ -547,12 +732,15 @@ static void clock_tree_show_one(struct seq_file *s, struct clk *c, int level) c->rate > c->max_rate ? '!' : ' ', !c->set ? '*' : ' ', 30 - level * 3, c->name, - state, c->refcnt, div, c->rate); + state, c->refcnt, div, clk_get_rate_all_locked(c)); list_for_each_entry(d, &c->dvfs, node) dvfs_show_one(s, d, level + 1); - list_for_each_entry_safe(child, safe, &c->children, sibling) { + list_for_each_entry(child, &clocks, node) { + if (child->parent != c) + continue; + clock_tree_show_one(s, child, level + 1); } } @@ -560,14 +748,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; } diff --git a/arch/arm/mach-tegra/clock.h b/arch/arm/mach-tegra/clock.h index b13aab47083e..0bcf475b3c82 100644 --- a/arch/arm/mach-tegra/clock.h +++ b/arch/arm/mach-tegra/clock.h @@ -21,6 +21,8 @@ #define __MACH_TEGRA_CLOCK_H #include +#include +#include #include #define DIV_BUS (1 << 0) @@ -75,8 +77,6 @@ enum clk_state { struct clk { /* node for master clocks list */ struct list_head node; /* node for list of all clocks */ - struct list_head children; /* list of children */ - struct list_head sibling; /* node for children */ struct list_head dvfs; /* list of dvfs dependencies */ struct clk_lookup lookup; @@ -91,11 +91,11 @@ struct clk { unsigned long max_rate; bool is_dvfs; bool auto_dvfs; + bool cansleep; u32 flags; const char *name; u32 refcnt; - unsigned long requested_rate; enum clk_state state; struct clk *parent; u32 div; @@ -137,8 +137,10 @@ struct clk { unsigned long rate; } shared_bus_user; } u; -}; + struct mutex mutex; + spinlock_t spinlock; +}; struct clk_duplicate { const char *name; @@ -158,12 +160,10 @@ void tegra2_periph_reset_assert(struct clk *c); void clk_init(struct clk *clk); struct clk *tegra_get_clock_by_name(const char *name); unsigned long clk_measure_input_freq(void); -void clk_disable_locked(struct clk *c); -int clk_enable_locked(struct clk *c); -int clk_set_parent_locked(struct clk *c, struct clk *parent); -int clk_set_rate_locked(struct clk *c, unsigned long rate); int clk_reparent(struct clk *c, struct clk *parent); void tegra_clk_init_from_table(struct tegra_clk_init_table *table); void tegra_clk_set_dvfs_rates(void); +void clk_set_cansleep(struct clk *c); +unsigned long clk_get_rate_locked(struct clk *c); #endif diff --git a/arch/arm/mach-tegra/include/mach/clk.h b/arch/arm/mach-tegra/include/mach/clk.h index 04ff7b672ad8..f96f8c7c53ee 100644 --- a/arch/arm/mach-tegra/include/mach/clk.h +++ b/arch/arm/mach-tegra/include/mach/clk.h @@ -26,5 +26,6 @@ void tegra_periph_reset_deassert(struct clk *c); void tegra_periph_reset_assert(struct clk *c); int tegra_dvfs_set_rate(struct clk *c, unsigned long rate); +unsigned long clk_get_rate_all_locked(struct clk *c); #endif diff --git a/arch/arm/mach-tegra/tegra2_clocks.c b/arch/arm/mach-tegra/tegra2_clocks.c index 7f5ed03fa958..8bc46e7e0988 100644 --- a/arch/arm/mach-tegra/tegra2_clocks.c +++ b/arch/arm/mach-tegra/tegra2_clocks.c @@ -23,7 +23,7 @@ #include #include #include -#include +#include #include @@ -337,12 +337,12 @@ static int tegra2_super_clk_set_parent(struct clk *c, struct clk *p) val |= sel->value << shift; if (c->refcnt) - clk_enable_locked(p); + clk_enable(p); clk_writel(val, c->reg); if (c->refcnt && c->parent) - clk_disable_locked(c->parent); + clk_disable(c->parent); clk_reparent(c, p); return 0; @@ -389,31 +389,31 @@ static int tegra2_cpu_clk_set_rate(struct clk *c, unsigned long rate) * Take an extra reference to the main pll so it doesn't turn * off when we move the cpu off of it */ - clk_enable_locked(c->u.cpu.main); + clk_enable(c->u.cpu.main); - ret = clk_set_parent_locked(c->parent, c->u.cpu.backup); + ret = clk_set_parent(c->parent, c->u.cpu.backup); if (ret) { pr_err("Failed to switch cpu to clock %s\n", c->u.cpu.backup->name); goto out; } - if (rate == c->u.cpu.backup->rate) + if (rate == clk_get_rate(c->u.cpu.backup)) goto out; - ret = clk_set_rate_locked(c->u.cpu.main, rate); + ret = clk_set_rate(c->u.cpu.main, rate); if (ret) { pr_err("Failed to change cpu pll to %lu\n", rate); goto out; } - ret = clk_set_parent_locked(c->parent, c->u.cpu.main); + ret = clk_set_parent(c->parent, c->u.cpu.main); if (ret) { pr_err("Failed to switch cpu to clock %s\n", c->u.cpu.main->name); goto out; } out: - clk_disable_locked(c->u.cpu.main); + clk_disable(c->u.cpu.main); return ret; } @@ -465,7 +465,7 @@ static void tegra2_bus_clk_disable(struct clk *c) static int tegra2_bus_clk_set_rate(struct clk *c, unsigned long rate) { u32 val = clk_readl(c->reg); - unsigned long parent_rate = c->parent->rate; + unsigned long parent_rate = clk_get_rate(c->parent); int i; for (i = 1; i <= 4; i++) { if (rate == parent_rate / i) { @@ -539,14 +539,15 @@ static void tegra2_blink_clk_disable(struct clk *c) static int tegra2_blink_clk_set_rate(struct clk *c, unsigned long rate) { - if (rate >= c->parent->rate) { + unsigned long parent_rate = clk_get_rate(c->parent); + if (rate >= parent_rate) { c->div = 1; pmc_writel(0, c->reg); } else { unsigned int on_off; u32 val; - on_off = DIV_ROUND_UP(c->parent->rate / 8, rate); + on_off = DIV_ROUND_UP(parent_rate / 8, rate); c->div = on_off * 8; val = (on_off & PMC_BLINK_TIMER_DATA_ON_MASK) << @@ -632,7 +633,7 @@ static int tegra2_pll_clk_set_rate(struct clk *c, unsigned long rate) pr_debug("%s: %s %lu\n", __func__, c->name, rate); - input_rate = c->parent->rate; + input_rate = clk_get_rate(c->parent); for (sel = c->u.pll.freq_table; sel->input_rate != 0; sel++) { if (sel->input_rate == input_rate && sel->output_rate == rate) { c->mul = sel->n; @@ -772,9 +773,11 @@ static int tegra2_pll_div_clk_set_rate(struct clk *c, unsigned long rate) u32 val; u32 new_val; int divider_u71; + unsigned long parent_rate = clk_get_rate(c->parent); + pr_debug("%s: %s %lu\n", __func__, c->name, rate); if (c->flags & DIV_U71) { - divider_u71 = clk_div71_get_divider(c->parent->rate, rate); + divider_u71 = clk_div71_get_divider(parent_rate, rate); if (divider_u71 >= 0) { val = clk_readl(c->reg); new_val = val >> c->reg_shift; @@ -792,7 +795,7 @@ static int tegra2_pll_div_clk_set_rate(struct clk *c, unsigned long rate) return 0; } } else if (c->flags & DIV_2) { - if (c->parent->rate == rate * 2) + if (parent_rate == rate * 2) return 0; } return -EINVAL; @@ -801,15 +804,16 @@ static int tegra2_pll_div_clk_set_rate(struct clk *c, unsigned long rate) static long tegra2_pll_div_clk_round_rate(struct clk *c, unsigned long rate) { int divider; + unsigned long parent_rate = clk_get_rate(c->parent); pr_debug("%s: %s %lu\n", __func__, c->name, rate); if (c->flags & DIV_U71) { - divider = clk_div71_get_divider(c->parent->rate, rate); + divider = clk_div71_get_divider(parent_rate, rate); if (divider < 0) return divider; - return c->parent->rate * 2 / (divider + 2); + return parent_rate * 2 / (divider + 2); } else if (c->flags & DIV_2) { - return c->parent->rate / 2; + return parent_rate / 2; } return -EINVAL; } @@ -923,12 +927,12 @@ static int tegra2_periph_clk_set_parent(struct clk *c, struct clk *p) val |= (sel->value) << PERIPH_CLK_SOURCE_SHIFT; if (c->refcnt) - clk_enable_locked(p); + clk_enable(p); clk_writel(val, c->reg); if (c->refcnt && c->parent) - clk_disable_locked(c->parent); + clk_disable(c->parent); clk_reparent(c, p); return 0; @@ -942,9 +946,10 @@ static int tegra2_periph_clk_set_rate(struct clk *c, unsigned long rate) { u32 val; int divider; - pr_debug("%s: %lu\n", __func__, rate); + unsigned long parent_rate = clk_get_rate(c->parent); + if (c->flags & DIV_U71) { - divider = clk_div71_get_divider(c->parent->rate, rate); + divider = clk_div71_get_divider(parent_rate, rate); if (divider >= 0) { val = clk_readl(c->reg); val &= ~PERIPH_CLK_SOURCE_DIVU71_MASK; @@ -955,7 +960,7 @@ static int tegra2_periph_clk_set_rate(struct clk *c, unsigned long rate) return 0; } } else if (c->flags & DIV_U16) { - divider = clk_div16_get_divider(c->parent->rate, rate); + divider = clk_div16_get_divider(parent_rate, rate); if (divider >= 0) { val = clk_readl(c->reg); val &= ~PERIPH_CLK_SOURCE_DIVU16_MASK; @@ -965,7 +970,7 @@ static int tegra2_periph_clk_set_rate(struct clk *c, unsigned long rate) c->mul = 1; return 0; } - } else if (c->parent->rate <= rate) { + } else if (parent_rate <= rate) { c->div = 1; c->mul = 1; return 0; @@ -977,19 +982,20 @@ static long tegra2_periph_clk_round_rate(struct clk *c, unsigned long rate) { int divider; + unsigned long parent_rate = clk_get_rate(c->parent); pr_debug("%s: %s %lu\n", __func__, c->name, rate); if (c->flags & DIV_U71) { - divider = clk_div71_get_divider(c->parent->rate, rate); + divider = clk_div71_get_divider(parent_rate, rate); if (divider < 0) return divider; - return c->parent->rate * 2 / (divider + 2); + return parent_rate * 2 / (divider + 2); } else if (c->flags & DIV_U16) { - divider = clk_div16_get_divider(c->parent->rate, rate); + divider = clk_div16_get_divider(parent_rate, rate); if (divider < 0) return divider; - return c->parent->rate / (divider + 1); + return parent_rate / (divider + 1); } return -EINVAL; } @@ -1017,7 +1023,7 @@ static void tegra2_clk_double_init(struct clk *c) static int tegra2_clk_double_set_rate(struct clk *c, unsigned long rate) { - if (rate != 2 * c->parent->rate) + if (rate != 2 * clk_get_rate(c->parent)) return -EINVAL; c->mul = 2; c->div = 1; @@ -1068,12 +1074,12 @@ static int tegra2_audio_sync_clk_set_parent(struct clk *c, struct clk *p) val |= sel->value; if (c->refcnt) - clk_enable_locked(p); + clk_enable(p); clk_writel(val, c->reg); if (c->refcnt && c->parent) - clk_disable_locked(c->parent); + clk_disable(c->parent); clk_reparent(c, p); return 0; @@ -1134,12 +1140,13 @@ static void tegra_clk_shared_bus_update(struct clk *bus) struct clk *c; unsigned long rate = bus->u.shared_bus.min_rate; - list_for_each_entry(c, &bus->u.shared_bus.list, u.shared_bus_user.node) + list_for_each_entry(c, &bus->u.shared_bus.list, + u.shared_bus_user.node) { if (c->u.shared_bus_user.enabled) rate = max(c->u.shared_bus_user.rate, rate); + } - if (rate != bus->rate) - clk_set_rate_locked(bus, rate); + clk_set_rate(bus, rate); }; static void tegra_clk_shared_bus_init(struct clk *c) From a1d72a522d5ecc98f6b57816f90141d1f0eecfeb Mon Sep 17 00:00:00 2001 From: Colin Cross Date: Mon, 1 Nov 2010 17:27:29 -0700 Subject: [PATCH 4/8] ARM: tegra: dvfs: Get rid of dvfs_lock and move init later Get rid of dvfs_lock, replacing it with the cansleep flag on clocks. Clocks with the cansleep flag set will lock a mutex before calling into dvfs. Also does the regulator api calls during late init, after the regulators have been probed. Signed-off-by: Colin Cross Change-Id: I5b8bd249bd4f3ae495f2076f1e6d2bfb38737f29 --- arch/arm/mach-tegra/clock.c | 1 + arch/arm/mach-tegra/dvfs.c | 84 +++++++++++++++++-------------- arch/arm/mach-tegra/dvfs.h | 4 +- arch/arm/mach-tegra/tegra2_dvfs.c | 3 +- 4 files changed, 49 insertions(+), 43 deletions(-) diff --git a/arch/arm/mach-tegra/clock.c b/arch/arm/mach-tegra/clock.c index 4486214c52f5..34c2c29fa760 100644 --- a/arch/arm/mach-tegra/clock.c +++ b/arch/arm/mach-tegra/clock.c @@ -568,6 +568,7 @@ int __init tegra_disable_boot_clocks(void) int __init tegra_late_init_clock(void) { + tegra_dvfs_late_init(); tegra_disable_boot_clocks(); tegra_clk_set_dvfs_rates(); return 0; diff --git a/arch/arm/mach-tegra/dvfs.c b/arch/arm/mach-tegra/dvfs.c index 0a2135e3b784..d29315aed0dc 100644 --- a/arch/arm/mach-tegra/dvfs.c +++ b/arch/arm/mach-tegra/dvfs.c @@ -42,21 +42,11 @@ struct dvfs_reg { int millivolts; }; -static LIST_HEAD(dvfs_list); static LIST_HEAD(dvfs_debug_list); static LIST_HEAD(dvfs_reg_list); -static DEFINE_MUTEX(dvfs_lock); - -void lock_dvfs(void) -{ - mutex_lock(&dvfs_lock); -} - -void unlock_dvfs(void) -{ - mutex_unlock(&dvfs_lock); -} +static DEFINE_MUTEX(dvfs_debug_list_lock); +static DEFINE_MUTEX(dvfs_reg_list_lock); static int dvfs_reg_set_voltage(struct dvfs_reg *dvfs_reg) { @@ -71,46 +61,53 @@ static int dvfs_reg_set_voltage(struct dvfs_reg *dvfs_reg) dvfs_reg->millivolts = millivolts; + if (!dvfs_reg->reg) { + pr_warn("dvfs set voltage on %s ignored\n", dvfs_reg->reg_id); + return 0; + } + return regulator_set_voltage(dvfs_reg->reg, millivolts * 1000, dvfs_reg->max_millivolts * 1000); } -static int dvfs_reg_get_voltage(struct dvfs_reg *dvfs_reg) +static int dvfs_reg_connect_to_regulator(struct dvfs_reg *dvfs_reg) { - int ret = regulator_get_voltage(dvfs_reg->reg); + struct regulator *reg; - if (ret > 0) - return ret / 1000; + if (!dvfs_reg->reg) { + reg = regulator_get(NULL, dvfs_reg->reg_id); + if (IS_ERR(reg)) + return -EINVAL; + } - return ret; + dvfs_reg->reg = reg; + + return 0; } static struct dvfs_reg *get_dvfs_reg(struct dvfs *d) { struct dvfs_reg *dvfs_reg; - struct regulator *reg; + + mutex_lock(&dvfs_reg_list_lock); list_for_each_entry(dvfs_reg, &dvfs_reg_list, node) if (!strcmp(d->reg_id, dvfs_reg->reg_id)) - return dvfs_reg; - - reg = regulator_get(NULL, d->reg_id); - if (IS_ERR(reg)) - return NULL; + goto out; dvfs_reg = kzalloc(sizeof(struct dvfs_reg), GFP_KERNEL); if (!dvfs_reg) { pr_err("%s: Failed to allocate dvfs_reg\n", __func__); - regulator_put(reg); - return NULL; + goto out; } INIT_LIST_HEAD(&dvfs_reg->dvfs); - dvfs_reg->reg = reg; dvfs_reg->reg_id = kstrdup(d->reg_id, GFP_KERNEL); list_add_tail(&dvfs_reg->node, &dvfs_reg_list); +out: + mutex_unlock(&dvfs_reg_list_lock); return dvfs_reg; } @@ -127,7 +124,7 @@ static struct dvfs_reg *attach_dvfs_reg(struct dvfs *d) if (d->max_millivolts > d->dvfs_reg->max_millivolts) d->dvfs_reg->max_millivolts = d->max_millivolts; - d->cur_millivolts = dvfs_reg_get_voltage(d->dvfs_reg); + d->cur_millivolts = d->max_millivolts; return dvfs_reg; } @@ -177,7 +174,7 @@ int tegra_dvfs_set_rate(struct clk *c, unsigned long rate) c->dvfs_rate = rate; - freq_up = (c->refcnt == 0) || (rate > c->rate); + freq_up = (c->refcnt == 0) || (rate > clk_get_rate_locked(c)); list_for_each_entry(d, &c->dvfs, node) { if (d->higher == freq_up) @@ -197,7 +194,8 @@ int tegra_dvfs_set_rate(struct clk *c, unsigned long rate) } EXPORT_SYMBOL(tegra_dvfs_set_rate); -int tegra_enable_dvfs_on_clk(struct clk *c, struct dvfs *d) +/* May only be called during clock init, does not take any locks on clock c. */ +int __init tegra_enable_dvfs_on_clk(struct clk *c, struct dvfs *d) { int i; struct dvfs_reg *dvfs_reg; @@ -221,30 +219,38 @@ int tegra_enable_dvfs_on_clk(struct clk *c, struct dvfs *d) } d->num_freqs = i; - if (d->auto_dvfs) + if (d->auto_dvfs) { c->auto_dvfs = true; + clk_set_cansleep(c); + } c->is_dvfs = true; - smp_wmb(); list_add_tail(&d->node, &c->dvfs); + mutex_lock(&dvfs_debug_list_lock); list_add_tail(&d->debug_node, &dvfs_debug_list); + mutex_unlock(&dvfs_debug_list_lock); return 0; } -int __init tegra_init_dvfs(void) +/* + * Iterate through all the dvfs regulators, finding the regulator exported + * by the regulator api for each one. Must be called in late init, after + * all the regulator api's regulators are initialized. + */ +int __init tegra_dvfs_late_init(void) { - lock_dvfs(); - tegra2_init_dvfs(); + struct dvfs_reg *dvfs_reg; - tegra_clk_set_dvfs_rates(); - unlock_dvfs(); + mutex_lock(&dvfs_reg_list_lock); + list_for_each_entry(dvfs_reg, &dvfs_reg_list, node) + dvfs_reg_connect_to_regulator(dvfs_reg); + mutex_unlock(&dvfs_reg_list_lock); return 0; } -late_initcall(tegra_init_dvfs); #ifdef CONFIG_DEBUG_FS static int dvfs_tree_sort_cmp(void *p, struct list_head *a, struct list_head *b) @@ -273,7 +279,7 @@ static int dvfs_tree_show(struct seq_file *s, void *data) seq_printf(s, " clock rate mV\n"); seq_printf(s, "--------------------------------\n"); - lock_dvfs(); + mutex_lock(&dvfs_debug_list_lock); list_sort(NULL, &dvfs_debug_list, dvfs_tree_sort_cmp); @@ -288,7 +294,7 @@ static int dvfs_tree_show(struct seq_file *s, void *data) d->cur_rate, d->cur_millivolts); } - unlock_dvfs(); + mutex_unlock(&dvfs_debug_list_lock); return 0; } diff --git a/arch/arm/mach-tegra/dvfs.h b/arch/arm/mach-tegra/dvfs.h index df6a3866d31b..e5eac6cf9cd0 100644 --- a/arch/arm/mach-tegra/dvfs.h +++ b/arch/arm/mach-tegra/dvfs.h @@ -49,11 +49,9 @@ struct dvfs { struct list_head reg_node; }; -void lock_dvfs(void); -void unlock_dvfs(void); - void tegra2_init_dvfs(void); int tegra_enable_dvfs_on_clk(struct clk *c, struct dvfs *d); int dvfs_debugfs_init(struct dentry *clk_debugfs_root); +int tegra_dvfs_late_init(void); #endif diff --git a/arch/arm/mach-tegra/tegra2_dvfs.c b/arch/arm/mach-tegra/tegra2_dvfs.c index 024e1b970638..4a831435991e 100644 --- a/arch/arm/mach-tegra/tegra2_dvfs.c +++ b/arch/arm/mach-tegra/tegra2_dvfs.c @@ -18,6 +18,7 @@ */ #include +#include #include #include "clock.h" @@ -132,7 +133,7 @@ static struct dvfs dvfs_init[] = { CORE_DVFS("NVRM_DEVID_CLK_SRC", 1, MHZ, 480, 600, 800, 1067, 1067), }; -void tegra2_init_dvfs(void) +void __init tegra2_init_dvfs(void) { int i; struct clk *c; From 34661b64da75b9990bcbee61203ea2fb76669625 Mon Sep 17 00:00:00 2001 From: Colin Cross Date: Mon, 1 Nov 2010 17:30:33 -0700 Subject: [PATCH 5/8] ARM: tegra: suspend: use clk_get_rate_all_locked Signed-off-by: Colin Cross Change-Id: I2d4bfd8728998903f9cff4a0f1ab41e76bdc02d7 --- arch/arm/mach-tegra/suspend.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/arch/arm/mach-tegra/suspend.c b/arch/arm/mach-tegra/suspend.c index 0d9898de0c4b..8c7d0a9ffe93 100644 --- a/arch/arm/mach-tegra/suspend.c +++ b/arch/arm/mach-tegra/suspend.c @@ -46,6 +46,7 @@ #include #include +#include #include #include #include @@ -358,7 +359,7 @@ unsigned int tegra_suspend_lp2(unsigned int us) writel(virt_to_phys(tegra_lp2_startup), evp_reset); set_power_timers(pdata->cpu_timer, pdata->cpu_off_timer, - clk_get_rate(tegra_pclk)); + clk_get_rate_all_locked(tegra_pclk)); if (us) tegra_lp2_set_trigger(us); From c41283c28bf8296cfe27f843a7fa287b4d2ab9dd Mon Sep 17 00:00:00 2001 From: Colin Cross Date: Wed, 3 Nov 2010 16:32:15 -0700 Subject: [PATCH 6/8] ARM: tegra: clock: Add a pass-through set_rate to super clocks Change-Id: I55027c93415a59cbf701b1f30e436203316c0d61 Signed-off-by: Colin Cross --- arch/arm/mach-tegra/tegra2_clocks.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/arch/arm/mach-tegra/tegra2_clocks.c b/arch/arm/mach-tegra/tegra2_clocks.c index 8bc46e7e0988..2c667fab39ab 100644 --- a/arch/arm/mach-tegra/tegra2_clocks.c +++ b/arch/arm/mach-tegra/tegra2_clocks.c @@ -351,11 +351,24 @@ static int tegra2_super_clk_set_parent(struct clk *c, struct clk *p) return -EINVAL; } +/* + * Super clocks have "clock skippers" instead of dividers. Dividing using + * a clock skipper does not allow the voltage to be scaled down, so instead + * adjust the rate of the parent clock. This requires that the parent of a + * super clock have no other children, otherwise the rate will change + * underneath the other children. + */ +static int tegra2_super_clk_set_rate(struct clk *c, unsigned long rate) +{ + return clk_set_rate(c->parent, rate); +} + static struct clk_ops tegra_super_ops = { .init = tegra2_super_clk_init, .enable = tegra2_super_clk_enable, .disable = tegra2_super_clk_disable, .set_parent = tegra2_super_clk_set_parent, + .set_rate = tegra2_super_clk_set_rate, }; /* virtual cpu clock functions */ From f0c185334383d9c65977a820acd06ecadaf3a070 Mon Sep 17 00:00:00 2001 From: Colin Cross Date: Thu, 4 Nov 2010 17:10:09 -0700 Subject: [PATCH 7/8] serial: tegra_hsuart: Fix sleeping while atomic Move the flush_work outside of stop_tx, do it during suspend and shutdown instead. Move the tty portion of the tx work into the dma complete callback, and only handle polling on the fifo and then requeuing the dma in the work function. Change-Id: I56db6504d35e5e7019379dc8a4323b4a27c72e59 Signed-off-by: Colin Cross --- drivers/serial/tegra_hsuart.c | 41 ++++++++++++++++++++++------------- 1 file changed, 26 insertions(+), 15 deletions(-) diff --git a/drivers/serial/tegra_hsuart.c b/drivers/serial/tegra_hsuart.c index 785cfb229779..292863ccaaa0 100644 --- a/drivers/serial/tegra_hsuart.c +++ b/drivers/serial/tegra_hsuart.c @@ -416,13 +416,9 @@ static void tegra_tx_dma_complete_work(struct work_struct *work) struct tegra_uart_port *t = container_of(work, struct tegra_uart_port, tx_work); struct tegra_dma_req *req = &t->tx_dma_req; - struct circ_buf *xmit = &t->uport.state->xmit; - int count = req->bytes_transferred; unsigned long flags; int timeout = 20; - dev_vdbg(t->uport.dev, "%s: %d\n", __func__, count); - while ((uart_readb(t, UART_LSR) & TX_EMPTY_STATUS) != TX_EMPTY_STATUS) { timeout--; if (timeout == 0) { @@ -434,11 +430,8 @@ static void tegra_tx_dma_complete_work(struct work_struct *work) } spin_lock_irqsave(&t->uport.lock, flags); - xmit->tail = (xmit->tail + count) & (UART_XMIT_SIZE - 1); - t->tx_in_progress = 0; - if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS) - uart_write_wakeup(&t->uport); + t->tx_in_progress = 0; if (req->status != -TEGRA_DMA_REQ_ERROR_ABORTED) tegra_start_next_tx(t); @@ -449,7 +442,21 @@ static void tegra_tx_dma_complete_work(struct work_struct *work) static void tegra_tx_dma_complete_callback(struct tegra_dma_req *req) { struct tegra_uart_port *t = req->dev; + struct circ_buf *xmit = &t->uport.state->xmit; + int count = req->bytes_transferred; + unsigned long flags; + + dev_vdbg(t->uport.dev, "%s: %d\n", __func__, count); + + spin_lock_irqsave(&t->uport.lock, flags); + xmit->tail = (xmit->tail + count) & (UART_XMIT_SIZE - 1); + + if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS) + uart_write_wakeup(&t->uport); + schedule_work(&t->tx_work); + + spin_unlock_irqrestore(&t->uport.lock, flags); } static irqreturn_t tegra_uart_isr(int irq, void *data) @@ -552,6 +559,9 @@ static void tegra_stop_rx(struct uart_port *u) static void tegra_uart_hw_deinit(struct tegra_uart_port *t) { unsigned char fcr; + unsigned long flags; + + flush_work(&t->tx_work); /* Disable interrupts */ uart_writeb(t, 0, UART_IER); @@ -559,6 +569,8 @@ static void tegra_uart_hw_deinit(struct tegra_uart_port *t) while ((uart_readb(t, UART_LSR) & UART_LSR_TEMT) != UART_LSR_TEMT); udelay(200); + spin_lock_irqsave(&t->uport.lock, flags); + /* Reset the Rx and Tx FIFOs */ fcr = t->fcr_shadow; fcr |= UART_FCR_CLEAR_XMIT | UART_FCR_CLEAR_RCVR; @@ -568,6 +580,8 @@ static void tegra_uart_hw_deinit(struct tegra_uart_port *t) clk_disable(t->clk); t->baud = 0; + + spin_unlock_irqrestore(&t->uport.lock, flags); } static void tegra_uart_free_rx_dma(struct tegra_uart_port *t) @@ -805,14 +819,11 @@ fail: static void tegra_shutdown(struct uart_port *u) { struct tegra_uart_port *t; - unsigned long flags; - spin_lock_irqsave(&u->lock, flags); t = container_of(u, struct tegra_uart_port, uport); dev_vdbg(u->dev, "+tegra_shutdown\n"); tegra_uart_hw_deinit(t); - spin_unlock_irqrestore(&u->lock, flags); t->rx_in_progress = 0; t->tx_in_progress = 0; @@ -947,11 +958,9 @@ static void tegra_stop_tx(struct uart_port *u) t = container_of(u, struct tegra_uart_port, uport); - if (t->use_tx_dma) { + if (t->use_tx_dma) tegra_dma_dequeue_req(t->tx_dma, &t->tx_dma_req); - flush_work(&t->tx_work); - } - t->tx_in_progress = 0; + return; } @@ -1170,6 +1179,8 @@ static int tegra_uart_suspend(struct platform_device *pdev, pm_message_t state) u = &t->uport; uart_suspend_port(&tegra_uart_driver, u); + + flush_work(&t->tx_work); return 0; } From 1b26bfc7ad7810c499b8b6d64e9a8293aa46b8d1 Mon Sep 17 00:00:00 2001 From: Colin Cross Date: Wed, 3 Nov 2010 11:42:20 -0700 Subject: [PATCH 8/8] ARM: tegra: common: Reduce sclk to 120 MHz Set pll_m_out1, sclk, and hclk to 120 MHz, and pclk to 60 MHz. Drivers that require a faster bus speed can use a shared bus clock reference to increase the sclk frequency up to 240 MHz, which will also increase pll_m_out1, hclk, and pclk. Change-Id: Ic491da80de1d95e6550cf9351eae185210f55b2a Signed-off-by: Colin Cross --- arch/arm/mach-tegra/common.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/arch/arm/mach-tegra/common.c b/arch/arm/mach-tegra/common.c index 82bca0ccb223..2fdb99b6fdc5 100644 --- a/arch/arm/mach-tegra/common.c +++ b/arch/arm/mach-tegra/common.c @@ -59,10 +59,10 @@ static __initdata struct tegra_clk_init_table common_clk_init_table[] = { { "pll_p_out1", "pll_p", 28800000, true }, { "pll_p_out2", "pll_p", 48000000, true }, { "pll_p_out3", "pll_p", 72000000, true }, - { "pll_m_out1", "pll_m", 240000000, true }, - { "sclk", "pll_m_out1", 240000000, true }, - { "hclk", "sclk", 240000000, true }, - { "pclk", "hclk", 120000000, true }, + { "pll_m_out1", "pll_m", 120000000, true }, + { "sclk", "pll_m_out1", 120000000, true }, + { "hclk", "sclk", 120000000, true }, + { "pclk", "hclk", 60000000, true }, { "pll_x", NULL, 0, true }, { "cpu", NULL, 0, true }, { "emc", NULL, 0, true },