summaryrefslogtreecommitdiff
path: root/arch/arm/mach-tegra
diff options
context:
space:
mode:
authorScott Williams <scwilliams@nvidia.com>2011-08-05 18:16:48 -0700
committerDan Willemsen <dwillemsen@nvidia.com>2011-11-30 21:47:03 -0800
commit4b848adaedc9e0c335b992fefefc16545a0ca7b1 (patch)
tree5cb52fb00add02a4fa666da3555c4e47ab765602 /arch/arm/mach-tegra
parent4182b75abce8204b13a783dd2f14d4aa63e58aaf (diff)
ARM: tegra: Fix mutex in atomic context when updating TWD freq
The CPU frequency change notifer runs in an atomic context but obtaining the current CPU frequency requires taking a mutex because updating the CPU frequency involves the regulator. Instead of directly parenting the TWD clock on the CPU clock, make the TWD a "detached child" of the CPU clock whose rate is updated whenever the CPU frequency changes. Change-Id: I49e15f85f269fb3ed0bcaee36ff739b4f064d6b8 Signed-off-by: Scott Williams <scwilliams@nvidia.com> Rebase-Id: R7aa10f2576752390464586bc629c972802beb989
Diffstat (limited to 'arch/arm/mach-tegra')
-rw-r--r--arch/arm/mach-tegra/clock.c15
-rw-r--r--arch/arm/mach-tegra/tegra2_clocks.c40
-rw-r--r--arch/arm/mach-tegra/tegra3_clocks.c55
3 files changed, 89 insertions, 21 deletions
diff --git a/arch/arm/mach-tegra/clock.c b/arch/arm/mach-tegra/clock.c
index 7ed1823fe089..65d92e1bca1a 100644
--- a/arch/arm/mach-tegra/clock.c
+++ b/arch/arm/mach-tegra/clock.c
@@ -645,8 +645,23 @@ void __init tegra_init_max_rate(struct clk *c, unsigned long max_rate)
void __init tegra_init_clock(void)
{
+ int ret;
+ struct clk *cpu;
+ struct clk *twd;
+
tegra_soc_init_clocks();
tegra_soc_init_dvfs();
+
+ /* The twd clock is a detached child of the CPU complex clock.
+ Force an update of the twd clock after DVFS as updated the
+ CPU clock rate. */
+ cpu = tegra_get_clock_by_name("cpu");
+ twd = tegra_get_clock_by_name("twd");
+ ret = clk_set_rate(twd, clk_get_rate(cpu));
+ if (ret)
+ pr_err("Failed to set twd clock rate: %d\n", ret);
+ else
+ pr_debug("TWD clock rate: %ld\n", clk_get_rate(twd));
}
#ifdef CONFIG_ARCH_TEGRA_2x_SOC
diff --git a/arch/arm/mach-tegra/tegra2_clocks.c b/arch/arm/mach-tegra/tegra2_clocks.c
index 4a511987c8a3..3c5561e02e11 100644
--- a/arch/arm/mach-tegra/tegra2_clocks.c
+++ b/arch/arm/mach-tegra/tegra2_clocks.c
@@ -384,6 +384,29 @@ static struct clk_ops tegra_super_ops = {
.set_rate = tegra2_super_clk_set_rate,
};
+static int tegra2_twd_clk_set_rate(struct clk *c, unsigned long rate)
+{
+ /* The input value 'rate' is the clock rate of the CPU complex. */
+ c->rate = (rate * c->mul) / c->div;
+ return 0;
+}
+
+static struct clk_ops tegra2_twd_ops = {
+ .set_rate = tegra2_twd_clk_set_rate,
+};
+
+static struct clk tegra2_clk_twd = {
+ /* NOTE: The twd clock must have *NO* parent. It's rate is directly
+ updated by tegra3_cpu_cmplx_clk_set_rate() because the
+ frequency change notifer for the twd is called in an
+ atomic context which cannot take a mutex. */
+ .name = "twd",
+ .ops = &tegra2_twd_ops,
+ .max_rate = 1000000000, /* Same as tegra_clk_virtual_cpu.max_rate */
+ .mul = 1,
+ .div = 4,
+};
+
/* virtual cpu clock functions */
/* some clocks can not be stopped (cpu, memory bus) while the SoC is running.
To change the frequency of these clocks, the parent pll may need to be
@@ -438,6 +461,12 @@ static int tegra2_cpu_clk_set_rate(struct clk *c, unsigned long rate)
goto out;
}
+ /* We can't parent the twd to directly to the CPU complex because
+ the TWD frequency update notifier is called in an atomic context
+ and the CPU frequency update requires a mutex. Update the twd
+ clock rate with the new CPU complex rate. */
+ clk_set_rate(&tegra2_clk_twd, clk_get_rate_locked(c));
+
out:
clk_disable(c->u.cpu.main);
return ret;
@@ -2069,15 +2098,6 @@ static struct clk tegra_clk_virtual_cpu = {
},
};
-static struct clk tegra_clk_twd = {
- .name = "twd",
- .parent = &tegra_clk_cclk,
- .ops = NULL,
- .max_rate = 250000000,
- .mul = 1,
- .div = 4,
-};
-
static struct clk tegra_clk_cop = {
.name = "cop",
.parent = &tegra_clk_sclk,
@@ -2452,7 +2472,7 @@ struct clk *tegra_ptr_clks[] = {
&tegra_clk_blink,
&tegra_clk_cop,
&tegra_clk_emc,
- &tegra_clk_twd,
+ &tegra2_clk_twd,
};
/* For some clocks maximum rate limits depend on tegra2 SKU */
diff --git a/arch/arm/mach-tegra/tegra3_clocks.c b/arch/arm/mach-tegra/tegra3_clocks.c
index bb431e6dccdc..4b338e9ca037 100644
--- a/arch/arm/mach-tegra/tegra3_clocks.c
+++ b/arch/arm/mach-tegra/tegra3_clocks.c
@@ -684,6 +684,29 @@ static struct clk_ops tegra_super_ops = {
.set_rate = tegra3_super_clk_set_rate,
};
+static int tegra3_twd_clk_set_rate(struct clk *c, unsigned long rate)
+{
+ /* The input value 'rate' is the clock rate of the CPU complex. */
+ c->rate = (rate * c->mul) / c->div;
+ return 0;
+}
+
+static struct clk_ops tegra3_twd_ops = {
+ .set_rate = tegra3_twd_clk_set_rate,
+};
+
+static struct clk tegra3_clk_twd = {
+ /* NOTE: The twd clock must have *NO* parent. It's rate is directly
+ updated by tegra3_cpu_cmplx_clk_set_rate() because the
+ frequency change notifer for the twd is called in an
+ atomic context which cannot take a mutex. */
+ .name = "twd",
+ .ops = &tegra3_twd_ops,
+ .max_rate = 1400000000, /* Same as tegra_clk_cpu_cmplx.max_rate */
+ .mul = 1,
+ .div = 2,
+};
+
/* virtual cpu clock functions */
/* some clocks can not be stopped (cpu, memory bus) while the SoC is running.
To change the frequency of these clocks, the parent pll may need to be
@@ -812,7 +835,26 @@ static void tegra3_cpu_cmplx_clk_disable(struct clk *c)
static int tegra3_cpu_cmplx_clk_set_rate(struct clk *c, unsigned long rate)
{
- return clk_set_rate(c->parent, rate);
+ unsigned long flags;
+ int ret;
+ struct clk *parent = c->parent;
+
+ if (!parent->ops || !parent->ops->set_rate)
+ return -ENOSYS;
+
+ clk_lock_save(parent, &flags);
+
+ ret = clk_set_rate_locked(parent, rate);
+
+ /* We can't parent the twd to directly to the CPU complex because
+ the TWD frequency update notifier is called in an atomic context
+ and the CPU frequency update requires a mutex. Update the twd
+ clock rate with the new CPU complex rate. */
+ clk_set_rate(&tegra3_clk_twd, clk_get_rate_locked(parent));
+
+ clk_unlock_restore(parent, &flags);
+
+ return ret;
}
static int tegra3_cpu_cmplx_clk_set_parent(struct clk *c, struct clk *p)
@@ -3268,15 +3310,6 @@ static struct clk tegra_clk_cpu_cmplx = {
.max_rate = 1400000000,
};
-static struct clk tegra_clk_twd = {
- .name = "twd",
- .parent = &tegra_clk_cpu_cmplx, /* FIXME??? */
- .ops = NULL,
- .max_rate = 400000000,
- .mul = 1,
- .div = 2,
-};
-
static struct clk tegra_clk_cop = {
.name = "cop",
.parent = &tegra_clk_sclk,
@@ -3695,7 +3728,7 @@ struct clk *tegra_ptr_clks[] = {
&tegra_clk_cop,
&tegra_clk_sbus_cmplx,
&tegra_clk_emc,
- &tegra_clk_twd,
+ &tegra3_clk_twd,
};
static struct tegra_edp_limits default_cpu_edp_limits[] = {