summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlex Frid <afrid@nvidia.com>2011-05-14 18:58:34 -0700
committerVarun Colbert <vcolbert@nvidia.com>2011-05-17 11:52:38 -0700
commitde4c80095b759924d0b8867918daa3dbc884c222 (patch)
treedb25d3c9b6142fa726250072e60005c3fc7aad51
parent8f12346fe6c4fbfe3a883a22b62b12ad14ffc30d (diff)
ARM: tegra: clock: Enable clock while setting rate/parent
When clock configuration (source mux, divider value) changes, the new control register setting does not take effect if clock is disabled. Later, when the clock is enabled it would run for several cycles on the old configuration before switching to the new one. This h/w behavior creates two problems: - since dvfs takes into account only new (enabled) rate, the module can be over-clocked during initial phase of the clock switch - since parent clock refcount is updated when the mux register was written, the parent clock maybe disabled by the time of actual switch and h/w would not be able to complete switch at all To avoid described problems clock is now always enabled while setting the new rate/parent (and disabled afterwards to keep refcount intact). Change-Id: I9bda56a2a98c9f3678715da1e1b8fe78874fb71e Reviewed-on: http://git-master/r/31640 Tested-by: Aleksandr Frid <afrid@nvidia.com> Reviewed-by: Jin Qian <jqian@nvidia.com> Reviewed-by: Scott Williams <scwilliams@nvidia.com>
-rw-r--r--arch/arm/mach-tegra/clock.c71
1 files changed, 58 insertions, 13 deletions
diff --git a/arch/arm/mach-tegra/clock.c b/arch/arm/mach-tegra/clock.c
index 1c0d774e447f..26c472fd0a7b 100644
--- a/arch/arm/mach-tegra/clock.c
+++ b/arch/arm/mach-tegra/clock.c
@@ -67,7 +67,7 @@
* 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
+ * on itself, except for clk_xxx_locked versions. 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
@@ -273,24 +273,21 @@ void clk_init(struct clk *c)
mutex_unlock(&clock_list_lock);
}
-int clk_enable(struct clk *c)
+static int clk_enable_locked(struct clk *c)
{
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;
+ return ret;
}
if (c->refcnt == 0) {
if (c->parent) {
ret = clk_enable(c->parent);
if (ret)
- goto out;
+ return ret;
}
if (c->ops && c->ops->enable) {
@@ -299,7 +296,7 @@ int clk_enable(struct clk *c)
if (ret) {
if (c->parent)
clk_disable(c->parent);
- goto out;
+ return ret;
}
c->state = ON;
c->set = true;
@@ -307,21 +304,27 @@ int clk_enable(struct clk *c)
clk_stats_update(c);
}
c->refcnt++;
-out:
- clk_unlock_restore(c, flags);
+
return ret;
}
-EXPORT_SYMBOL(clk_enable);
-void clk_disable(struct clk *c)
+
+int clk_enable(struct clk *c)
{
+ int ret = 0;
unsigned long flags;
clk_lock_save(c, flags);
+ ret = clk_enable_locked(c);
+ clk_unlock_restore(c, flags);
+ return ret;
+}
+EXPORT_SYMBOL(clk_enable);
+static 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) {
@@ -339,7 +342,14 @@ void clk_disable(struct clk *c)
if (clk_is_auto_dvfs(c) && c->refcnt == 0)
tegra_dvfs_set_rate(c, 0);
+}
+
+void clk_disable(struct clk *c)
+{
+ unsigned long flags;
+ clk_lock_save(c, flags);
+ clk_disable_locked(c);
clk_unlock_restore(c, flags);
}
EXPORT_SYMBOL(clk_disable);
@@ -350,6 +360,7 @@ int clk_set_parent(struct clk *c, struct clk *parent)
unsigned long flags;
unsigned long new_rate;
unsigned long old_rate;
+ bool disable = false;
clk_lock_save(c, flags);
@@ -371,6 +382,20 @@ int clk_set_parent(struct clk *c, struct clk *parent)
#endif
}
+ /* The new clock control register setting does not take effect if
+ * clock is disabled. Later, when the clock is enabled it would run
+ * for several cycles on the old parent, which may hang h/w if the
+ * parent is already disabled. To guarantee h/w switch to the new
+ * setting enable clock while setting parent.
+ */
+ if ((c->refcnt == 0) && (c->flags & MUX)) {
+ pr_warn("Setting parent of clock %s with refcnt 0", c->name);
+ disable = true;
+ ret = clk_enable_locked(c);
+ if (ret)
+ goto out;
+ }
+
if (clk_is_auto_dvfs(c) && c->refcnt > 0 &&
(!c->parent || new_rate > old_rate)) {
ret = tegra_dvfs_set_rate(c, new_rate);
@@ -387,6 +412,8 @@ int clk_set_parent(struct clk *c, struct clk *parent)
ret = tegra_dvfs_set_rate(c, new_rate);
out:
+ if (disable)
+ clk_disable_locked(c);
clk_unlock_restore(c, flags);
return ret;
}
@@ -404,6 +431,7 @@ int clk_set_rate(struct clk *c, unsigned long rate)
unsigned long flags;
unsigned long old_rate, max_rate;
long new_rate;
+ bool disable = false;
clk_lock_save(c, flags);
@@ -429,6 +457,21 @@ int clk_set_rate(struct clk *c, unsigned long rate)
rate = new_rate;
}
+ /* The new clock control register setting does not take effect if
+ * clock is disabled. Later, when the clock is enabled it would run
+ * for several cycles on the old rate, which may over-clock module
+ * at given voltage. To guarantee h/w switch to the new setting
+ * enable clock while setting rate.
+ */
+ if ((c->refcnt == 0) && (c->flags & (DIV_U71 | DIV_U16)) &&
+ clk_is_auto_dvfs(c)) {
+ pr_warn("Setting rate of clock %s with refcnt 0", c->name);
+ disable = true;
+ ret = clk_enable_locked(c);
+ if (ret)
+ goto out;
+ }
+
if (clk_is_auto_dvfs(c) && rate > old_rate && c->refcnt > 0) {
ret = tegra_dvfs_set_rate(c, rate);
if (ret)
@@ -444,6 +487,8 @@ int clk_set_rate(struct clk *c, unsigned long rate)
ret = tegra_dvfs_set_rate(c, rate);
out:
+ if (disable)
+ clk_disable_locked(c);
clk_unlock_restore(c, flags);
return ret;
}