summaryrefslogtreecommitdiff
path: root/arch/arm/mach-tegra/clock.c
diff options
context:
space:
mode:
authorAlex Frid <afrid@nvidia.com>2011-05-14 18:58:34 -0700
committerDan Willemsen <dwillemsen@nvidia.com>2011-11-30 21:42:38 -0800
commit2ecc37c958da5789b97c5434b67d12bafc0f3481 (patch)
treeacfed1e8ab01825faa002fe48bb264f622c69068 /arch/arm/mach-tegra/clock.c
parentc22db7786d08a6224be75b8e836c4547d4c696a8 (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). Original-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> Rebase-Id: R9964ee8633a54e55e5b26d578487a6d0e02c4fe4
Diffstat (limited to 'arch/arm/mach-tegra/clock.c')
-rw-r--r--arch/arm/mach-tegra/clock.c79
1 files changed, 62 insertions, 17 deletions
diff --git a/arch/arm/mach-tegra/clock.c b/arch/arm/mach-tegra/clock.c
index acebec3ed3e5..e4670aad76c9 100644
--- a/arch/arm/mach-tegra/clock.c
+++ b/arch/arm/mach-tegra/clock.c
@@ -66,9 +66,8 @@
* 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.
+ * on itself, except for clk_xxx_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
@@ -234,24 +233,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) {
@@ -260,7 +256,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;
@@ -268,21 +264,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) {
@@ -300,7 +302,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);
@@ -311,6 +320,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);
@@ -332,6 +342,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);
@@ -348,6 +372,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;
}
@@ -364,6 +390,7 @@ int clk_set_rate_locked(struct clk *c, unsigned long rate)
int ret = 0;
unsigned long old_rate, max_rate;
long new_rate;
+ bool disable = false;
old_rate = clk_get_rate_locked(c);
@@ -382,20 +409,38 @@ int clk_set_rate_locked(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)
- return ret;
+ goto out;
}
trace_clock_set_rate(c->name, rate, smp_processor_id());
ret = c->ops->set_rate(c, rate);
if (ret)
- return ret;
+ goto out;
if (clk_is_auto_dvfs(c) && rate < old_rate && c->refcnt > 0)
ret = tegra_dvfs_set_rate(c, rate);
+out:
+ if (disable)
+ clk_disable_locked(c);
return ret;
}