diff options
author | Scott Williams <scwilliams@nvidia.com> | 2011-08-05 18:16:48 -0700 |
---|---|---|
committer | Dan Willemsen <dwillemsen@nvidia.com> | 2011-11-30 21:47:03 -0800 |
commit | 4b848adaedc9e0c335b992fefefc16545a0ca7b1 (patch) | |
tree | 5cb52fb00add02a4fa666da3555c4e47ab765602 /arch/arm/mach-tegra | |
parent | 4182b75abce8204b13a783dd2f14d4aa63e58aaf (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.c | 15 | ||||
-rw-r--r-- | arch/arm/mach-tegra/tegra2_clocks.c | 40 | ||||
-rw-r--r-- | arch/arm/mach-tegra/tegra3_clocks.c | 55 |
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[] = { |