This series is just informational and illustrates how to use some of the common struct clk features. I wrote this code when testing the common struct patches. These are hacks and put some of code to use practically.
Firstly this series models the clocks between the MPU PLL and the ARM IP a bit more closely. Then CPUfreq is changed to use this new mpu_clk, which passes the rate change up to the PLL.
Another new clk, mpu_periphclk, is introduced which divides mpu_clk by 2. The recent smp_twd code from Linus W. is modified to use clk rate change notifiers for mpu_periphclk instead of CPUfreq notifiers.
Finally this series introduces a new OPP for the MPU on OMAP4 which bypasses the MPU DPLL and exercies some of the clk frameworks parent switching code. Using this OPP is probably unsafe and will permanently ruin your board, your desk and your home.
You can find the common struct clk patches at, https://lkml.org/lkml/2011/12/13/451
OMAP support for the common struct clk is needed for this series and can be found at, http://article.gmane.org/gmane.linux.ports.arm.omap/68217
These patches can be found at, http://git.linaro.org/gitweb?p=people/mturquette/linux.git%3Ba=shortlog%3Bh=... git://git.linaro.org/people/mturquette/linux.git v3.2-rc5-clkv4-omap-pm-testing
Linus Walleij (1): smp_twd: Reconfigure clockevents after cpufreq change
Mike Turquette (5): HACK: omap: opp: add fake 400MHz OPP to bypass MPU omap: clk: .round_rate for propagating to parents HACK: omap: clk: add mpu_periphclk clk node HACK: cpufreq: omap: change mpu_clk's rate HACK: arm: reprogram twd based on clk notifier
arch/arm/kernel/smp_twd.c | 89 +++++++++++++++++++++++++++++++-- arch/arm/mach-omap2/clkt_clksel.c | 8 +++ arch/arm/mach-omap2/clock.h | 2 + arch/arm/mach-omap2/clock44xx_data.c | 37 ++++++++++++++- arch/arm/mach-omap2/opp4xxx_data.c | 9 ++++ drivers/cpufreq/omap-cpufreq.c | 2 +- 6 files changed, 139 insertions(+), 8 deletions(-)
The following patch is only for testing __clk_reparent as part of the new common struct clk stuff. It may make your board burst into flames or otherwise void various warrantees.
This patch introduces a 400MHz OPP for the MPU, which happens to correspond to the bypass clk rate on the 4430 Panda (with 38.4MHz SYS_CLK). Using CPUfreq to set the MPU to this rate puts the MPU into Low Power Bypass, which triggers the __clk_reparent code in drivers/clk/clk.c, which migrates the dpll_mpu_ck directory (and all of its subdirs) to the div_mpu_hs_clk dir under dpll_core_ck.
Not-signed-off-by: Mike Turquette mturquette@ti.com --- arch/arm/mach-omap2/opp4xxx_data.c | 9 +++++++++ 1 files changed, 9 insertions(+), 0 deletions(-)
diff --git a/arch/arm/mach-omap2/opp4xxx_data.c b/arch/arm/mach-omap2/opp4xxx_data.c index 2293ba2..f1da758 100644 --- a/arch/arm/mach-omap2/opp4xxx_data.c +++ b/arch/arm/mach-omap2/opp4xxx_data.c @@ -68,6 +68,15 @@ struct omap_volt_data omap44xx_vdd_core_volt_data[] = { static struct omap_opp_def __initdata omap44xx_opp_def_list[] = { /* MPU OPP1 - OPP50 */ OPP_INITIALIZER("mpu", true, 300000000, OMAP4430_VDD_MPU_OPP50_UV), + /* + * MPU OPP1.5 - 400MHz - completely FAKE - not endorsed by TI + * + * DPLL_MPU is in Low Power Bypass driven by DPLL_CORE. After + * transitioning to this OPP you can see the migration in debugfs: + * /d/clk/virt_38400000_ck/sys_clkin_ck/dpll_mpu_ck to + * /d/.../dpll_core_ck/dpll_core_x2_ck/dpll_core_m5x2_ck/div_mpu_hs_clk + */ + OPP_INITIALIZER("mpu", true, 400000000, 1100000), /* MPU OPP2 - OPP100 */ OPP_INITIALIZER("mpu", true, 600000000, OMAP4430_VDD_MPU_OPP100_UV), /* MPU OPP3 - OPP-Turbo */
This patch introduces omap2_passthrough_round_rate which is a new .round_rate that works with the generic struct clk_ops defined in include/linux/clk.h.
The purpose of this .round_rate is to allow an OMAP clk to make no changes to its own dividers (if applicable) and instead defer the rate change up to the clk's parent.
Not-signed-off-by: Mike Turquette mturquette@ti.com --- arch/arm/mach-omap2/clkt_clksel.c | 8 ++++++++ arch/arm/mach-omap2/clock.h | 2 ++ 2 files changed, 10 insertions(+), 0 deletions(-)
diff --git a/arch/arm/mach-omap2/clkt_clksel.c b/arch/arm/mach-omap2/clkt_clksel.c index ad3befb..0f151ce 100644 --- a/arch/arm/mach-omap2/clkt_clksel.c +++ b/arch/arm/mach-omap2/clkt_clksel.c @@ -438,6 +438,14 @@ long omap2_clksel_round_rate(struct clk *clk, unsigned long target_rate, return omap2_clksel_round_rate_div(oclk, target_rate, &new_div); }
+long omap2_passthrough_round_rate(struct clk *clk, unsigned long target_rate, + unsigned long *parent_rate) +{ + *parent_rate = target_rate; + + return clk->rate; +} + /** * omap2_clksel_set_rate() - program clock rate in hardware * @clk: struct clk * to program rate diff --git a/arch/arm/mach-omap2/clock.h b/arch/arm/mach-omap2/clock.h index e03060e..05f8a00 100644 --- a/arch/arm/mach-omap2/clock.h +++ b/arch/arm/mach-omap2/clock.h @@ -86,6 +86,8 @@ struct clk *omap2_init_clksel_parent(struct clk *clk); unsigned long omap2_clksel_recalc(struct clk *clk); long omap2_clksel_round_rate(struct clk *clk, unsigned long target_rate, unsigned long *parent_rate); +long omap2_passthrough_round_rate(struct clk *clk, unsigned long target_rate, + unsigned long *parent_rate); int omap2_clksel_set_rate(struct clk *clk, unsigned long rate); int omap2_clksel_set_parent(struct clk *clk, struct clk *new_parent);
From: Mike Turquette mturquette@linaro.org
The ARM periphclk drives various peripherals for the MPU including the TWD and local timers. This patch creates the missing clk tree data to represent this relationship:
dpll_mpu_ck | dpll_mpu_m2_ck (divide by 1) | mpu_clk (divide by 1) | mpu_periphclk (divide by 2)
This patch is based on Santosh Shilimkar's original version: http://article.gmane.org/gmane.linux.ports.arm.omap/64936
Not-signed-off-by: Mike Turquette mturquette@ti.com --- arch/arm/mach-omap2/clock44xx_data.c | 37 +++++++++++++++++++++++++++++++++- 1 files changed, 36 insertions(+), 1 deletions(-)
diff --git a/arch/arm/mach-omap2/clock44xx_data.c b/arch/arm/mach-omap2/clock44xx_data.c index 33249b3..6c6d0fb 100644 --- a/arch/arm/mach-omap2/clock44xx_data.c +++ b/arch/arm/mach-omap2/clock44xx_data.c @@ -1035,7 +1035,7 @@ static const struct clksel dpll_mpu_m2_div[] = {
static const struct clk_hw_ops dpll_mpu_m2_ck_ops = { .recalc_rate = &omap2_clksel_recalc, - .round_rate = &omap2_clksel_round_rate, + .round_rate = &omap2_passthrough_round_rate, .set_rate = &omap2_clksel_set_rate, .get_parent = &omap2_get_parent_fixed, }; @@ -1043,6 +1043,7 @@ static const struct clk_hw_ops dpll_mpu_m2_ck_ops = { static struct clk_hw_omap dpll_mpu_m2_ck_hw = { .clk = { .name = "dpll_mpu_m2_ck", + .flags = CLK_PARENT_SET_RATE, .ops = &dpll_mpu_m2_ck_ops, }, .fixed_parent = &dpll_mpu_ck_hw.clk, @@ -1053,6 +1054,38 @@ static struct clk_hw_omap dpll_mpu_m2_ck_hw = { .deny_idle = &omap4_dpllmx_deny_gatectrl, };
+static const struct clk_hw_ops mpu_clk_ops = { + .recalc_rate = &omap_fixed_divisor_recalc, + .round_rate = &omap2_passthrough_round_rate, + .get_parent = &omap2_get_parent_fixed, +}; + +static struct clk_hw_omap mpu_clk_hw = { + .clk = { + .name = "mpu_clk", + .flags = CLK_PARENT_SET_RATE, + .ops = &mpu_clk_ops, + }, + .fixed_parent = &dpll_mpu_m2_ck_hw.clk, + .fixed_div = 1, +}; + +static const struct clk_hw_ops mpu_periphclk_ops = { + .recalc_rate = &omap_fixed_divisor_recalc, + .round_rate = &omap2_passthrough_round_rate, + .get_parent = &omap2_get_parent_fixed, +}; + +static struct clk_hw_omap mpu_periphclk_hw = { + .clk = { + .name = "mpu_periphclk", + .flags = CLK_PARENT_SET_RATE, + .ops = &mpu_periphclk_ops, + }, + .fixed_parent = &mpu_clk_hw.clk, + .fixed_div = 2, +}; + static const struct clk_hw_ops per_hs_clk_div_ck_ops = { .recalc_rate = &omap_fixed_divisor_recalc, .get_parent = &omap2_get_parent_fixed, @@ -4071,6 +4104,8 @@ static struct omap_clk omap44xx_clks[] = { CLK(NULL, "dpll_iva_m5x2_ck", &dpll_iva_m5x2_ck_hw.clk, CK_443X), CLK(NULL, "dpll_mpu_ck", &dpll_mpu_ck_hw.clk, CK_443X), CLK(NULL, "dpll_mpu_m2_ck", &dpll_mpu_m2_ck_hw.clk, CK_443X), + CLK(NULL, "mpu_clk", &mpu_clk_hw.clk, CK_443X), + CLK("smp_twd", NULL, &mpu_periphclk_hw.clk, CK_443X), CLK(NULL, "per_hs_clk_div_ck", &per_hs_clk_div_ck_hw.clk, CK_443X), CLK(NULL, "per_hsd_byp_clk_mux_ck", &per_hsd_byp_clk_mux_ck_hw.clk, CK_443X), CLK(NULL, "dpll_per_ck", &dpll_per_ck_hw.clk, CK_443X),
As a proof of concept, change OMAP's cpufreq driver to use mpu_clk in place of directly using the DPLL. This better reflects reality as there are some functional clks between the ARM IP and the PLL.
To make this work mpu_clk uses the new .round_rate function omap2_passthrough_round_rate, which simplies passing up a rate change request to the parent.
Not for merging, just to get discussion going around the common clk patches and future OMAP adaptations.
Not-signed-off-by: Mike Turquette mturquette@ti.com --- drivers/cpufreq/omap-cpufreq.c | 2 +- 1 files changed, 1 insertions(+), 1 deletions(-)
diff --git a/drivers/cpufreq/omap-cpufreq.c b/drivers/cpufreq/omap-cpufreq.c index 5d04c57..45cb7f9 100644 --- a/drivers/cpufreq/omap-cpufreq.c +++ b/drivers/cpufreq/omap-cpufreq.c @@ -247,7 +247,7 @@ static int __init omap_cpufreq_init(void) else if (cpu_is_omap34xx()) mpu_clk_name = "dpll1_ck"; else if (cpu_is_omap44xx()) - mpu_clk_name = "dpll_mpu_ck"; + mpu_clk_name = "mpu_clk";
if (!mpu_clk_name) { pr_err("%s: unsupported Silicon?\n", __func__);
From: Linus Walleij <(address hidden)>
The localtimer's clock changes with the cpu clock. After a cpufreq transition, update the clockevent's frequency and reprogram the next clock event.
Adds a clock called "smp_twd" that is used to determine the twd frequency, which can also be used at init time to avoid calibrating the twd frequency.
Clock changes are based on Rob Herring's work. The necessary changes in the clockevents framework was done by Thomas Gleixner in kernel v3.0.
Signed-off-by: Colin Cross <(address hidden)> Cc: Russell King <(address hidden)> Acked-by: Thomas Gleixner <(address hidden)> Acked-by: Rob Herring <(address hidden)> Acked-by: Santosh Shilimkar <(address hidden)> [ifdef:ed CPUfreq stuff for non-cpufreq configs] Signed-off-by: Linus Walleij <(address hidden)> --- arch/arm/kernel/smp_twd.c | 89 ++++++++++++++++++++++++++++++++++++++++++--- 1 files changed, 83 insertions(+), 6 deletions(-)
diff --git a/arch/arm/kernel/smp_twd.c b/arch/arm/kernel/smp_twd.c index a8a6682..92dbd80 100644 --- a/arch/arm/kernel/smp_twd.c +++ b/arch/arm/kernel/smp_twd.c @@ -10,13 +10,17 @@ */ #include <linux/init.h> #include <linux/kernel.h> +#include <linux/clk.h> +#include <linux/cpufreq.h> #include <linux/delay.h> #include <linux/device.h> +#include <linux/err.h> #include <linux/smp.h> #include <linux/jiffies.h> #include <linux/clockchips.h> #include <linux/irq.h> #include <linux/io.h> +#include <linux/percpu.h>
#include <asm/smp_twd.h> #include <asm/localtimer.h> @@ -25,7 +29,9 @@ /* set up by the platform code */ void __iomem *twd_base;
+static struct clk *twd_clk; static unsigned long twd_timer_rate; +static DEFINE_PER_CPU(struct clock_event_device *, twd_ce);
static struct clock_event_device __percpu **twd_evt;
@@ -89,6 +95,52 @@ void twd_timer_stop(struct clock_event_device *clk) disable_percpu_irq(clk->irq); }
+#ifdef CONFIG_CPU_FREQ + +/* + * Updates clockevent frequency when the cpu frequency changes. + * Called on the cpu that is changing frequency with interrupts disabled. + */ +static void twd_update_frequency(void *data) +{ + twd_timer_rate = clk_get_rate(twd_clk); + + clockevents_update_freq(__get_cpu_var(twd_ce), twd_timer_rate); +} + +static int twd_cpufreq_transition(struct notifier_block *nb, + unsigned long state, void *data) +{ + struct cpufreq_freqs *freqs = data; + + /* + * The twd clock events must be reprogrammed to account for the new + * frequency. The timer is local to a cpu, so cross-call to the + * changing cpu. + */ + if (state == CPUFREQ_POSTCHANGE || state == CPUFREQ_RESUMECHANGE) + smp_call_function_single(freqs->cpu, twd_update_frequency, + NULL, 1); + + return NOTIFY_OK; +} + +static struct notifier_block twd_cpufreq_nb = { + .notifier_call = twd_cpufreq_transition, +}; + +static int twd_cpufreq_init(void) +{ + if (!IS_ERR_OR_NULL(twd_clk)) + return cpufreq_register_notifier(&twd_cpufreq_nb, + CPUFREQ_TRANSITION_NOTIFIER); + + return 0; +} +core_initcall(twd_cpufreq_init); + +#endif + static void __cpuinit twd_calibrate_rate(void) { unsigned long count; @@ -140,6 +192,27 @@ static irqreturn_t twd_handler(int irq, void *dev_id) return IRQ_NONE; }
+static struct clk *twd_get_clock(void) +{ + struct clk *clk; + int err; + + clk = clk_get_sys("smp_twd", NULL); + if (IS_ERR(clk)) { + pr_err("smp_twd: clock not found: %d\n", (int)PTR_ERR(clk)); + return clk; + } + + err = clk_enable(clk); + if (err) { + pr_err("smp_twd: clock failed to enable: %d\n", err); + clk_put(clk); + return ERR_PTR(err); + } + + return clk; +} + /* * Setup the local clock events for a CPU. */ @@ -165,7 +238,13 @@ void __cpuinit twd_timer_setup(struct clock_event_device *clk) } }
- twd_calibrate_rate(); + if (!twd_clk) + twd_clk = twd_get_clock(); + + if (!IS_ERR_OR_NULL(twd_clk)) + twd_timer_rate = clk_get_rate(twd_clk); + else + twd_calibrate_rate();
clk->name = "local_timer"; clk->features = CLOCK_EVT_FEAT_PERIODIC | CLOCK_EVT_FEAT_ONESHOT | @@ -173,15 +252,13 @@ void __cpuinit twd_timer_setup(struct clock_event_device *clk) clk->rating = 350; clk->set_mode = twd_set_mode; clk->set_next_event = twd_set_next_event; - clk->shift = 20; - clk->mult = div_sc(twd_timer_rate, NSEC_PER_SEC, clk->shift); - clk->max_delta_ns = clockevent_delta2ns(0xffffffff, clk); - clk->min_delta_ns = clockevent_delta2ns(0xf, clk);
this_cpu_clk = __this_cpu_ptr(twd_evt); *this_cpu_clk = clk;
- clockevents_register_device(clk); + __get_cpu_var(twd_ce) = clk;
+ clockevents_config_and_register(clk, twd_timer_rate, + 0xf, 0xffffffff); enable_percpu_irq(clk->irq, 0); }
From: Mike Turquette mturquette@linaro.org
As a proof-of-concept, convert the existing smp_twd code to use clk notifiers in place of CPUfreq notifiers. This works out nicely for Cortex-A9 MPcore designs that scale all CPUs at the same frequency. For chips which can scale frequency independently this change makes less sense. (but I don't know how they were doing it in the first place...)
The primary purpose behind this change is to test the new clk notifier code. If you find it useful beyond that, please let me know!
Not-signed-off-by: Mike Turquette mturquette@linaro.org --- arch/arm/kernel/smp_twd.c | 40 ++++++++++++++++++++-------------------- 1 files changed, 20 insertions(+), 20 deletions(-)
diff --git a/arch/arm/kernel/smp_twd.c b/arch/arm/kernel/smp_twd.c index 92dbd80..8bdbaad 100644 --- a/arch/arm/kernel/smp_twd.c +++ b/arch/arm/kernel/smp_twd.c @@ -11,7 +11,6 @@ #include <linux/init.h> #include <linux/kernel.h> #include <linux/clk.h> -#include <linux/cpufreq.h> #include <linux/delay.h> #include <linux/device.h> #include <linux/err.h> @@ -95,51 +94,45 @@ void twd_timer_stop(struct clock_event_device *clk) disable_percpu_irq(clk->irq); }
-#ifdef CONFIG_CPU_FREQ - /* * Updates clockevent frequency when the cpu frequency changes. * Called on the cpu that is changing frequency with interrupts disabled. */ -static void twd_update_frequency(void *data) +static void twd_update_frequency(void *new_rate) { - twd_timer_rate = clk_get_rate(twd_clk); + unsigned long *rate = new_rate;
- clockevents_update_freq(__get_cpu_var(twd_ce), twd_timer_rate); + clockevents_update_freq(__get_cpu_var(twd_ce), *rate/1000); }
-static int twd_cpufreq_transition(struct notifier_block *nb, - unsigned long state, void *data) +static int twd_rate_change(struct notifier_block *nb, + unsigned long flags, void *data) { - struct cpufreq_freqs *freqs = data; + struct clk_notifier_data *cnd = data;
/* * The twd clock events must be reprogrammed to account for the new * frequency. The timer is local to a cpu, so cross-call to the * changing cpu. */ - if (state == CPUFREQ_POSTCHANGE || state == CPUFREQ_RESUMECHANGE) - smp_call_function_single(freqs->cpu, twd_update_frequency, - NULL, 1); + if (flags == POST_RATE_CHANGE) + smp_call_function(twd_update_frequency, (void *)&cnd->new_rate, 1);
return NOTIFY_OK; }
-static struct notifier_block twd_cpufreq_nb = { - .notifier_call = twd_cpufreq_transition, +static struct notifier_block twd_clk_nb = { + .notifier_call = twd_rate_change, };
-static int twd_cpufreq_init(void) +static int twd_clk_init(void) { if (!IS_ERR_OR_NULL(twd_clk)) - return cpufreq_register_notifier(&twd_cpufreq_nb, - CPUFREQ_TRANSITION_NOTIFIER); + return clk_notifier_register(twd_clk, &twd_clk_nb);
return 0; } -core_initcall(twd_cpufreq_init); - -#endif +core_initcall(twd_clk_init);
static void __cpuinit twd_calibrate_rate(void) { @@ -203,6 +196,13 @@ static struct clk *twd_get_clock(void) return clk; }
+ err = clk_prepare(clk); + if (err) { + pr_err("smp_twd: clock failed to prepare: %d\n", err); + clk_put(clk); + return ERR_PTR(err); + } + err = clk_enable(clk); if (err) { pr_err("smp_twd: clock failed to enable: %d\n", err);
Hi Mike,
I just sent new patches for the TWD CPUfreq stuff, including the bug fix you provide below for clk_prepare(). They're in Russell's patch tracker: http://www.arm.linux.org.uk/developer/patches/viewpatch.php?id=7210/1 http://www.arm.linux.org.uk/developer/patches/viewpatch.php?id=7211/1 http://www.arm.linux.org.uk/developer/patches/viewpatch.php?id=7212/1
On Wed, Dec 14, 2011 at 5:31 AM, Mike Turquette mturquette@ti.com wrote:
The primary purpose behind this change is to test the new clk notifier code. If you find it useful beyond that, please let me know!
This is way more elegant that the cpufreq way of doing things. For several years I was confused by TI:s way of re-reading any peripheral clock speed in CPUfreq notifiers when there was strictly no theoretical requirement for the CPU to actually change it's frequency when the clock frequency for that particular device changed.
See for example drivers/mmc/host/davinci_mmc.c:
#ifdef CONFIG_CPU_FREQ static int mmc_davinci_cpufreq_transition(struct notifier_block *nb, unsigned long val, void *data) { struct mmc_davinci_host *host; unsigned int mmc_pclk; struct mmc_host *mmc; unsigned long flags;
host = container_of(nb, struct mmc_davinci_host, freq_transition); mmc = host->mmc; mmc_pclk = clk_get_rate(host->clk);
if (val == CPUFREQ_POSTCHANGE) { spin_lock_irqsave(&mmc->lock, flags); host->mmc_input_clk = mmc_pclk; calculate_clk_divider(mmc, &mmc->ios); spin_unlock_irqrestore(&mmc->lock, flags); }
return 0; }
...what does the CPU has to do with the MMC host->clk again? Absolutely nothing. There is no point in the MMC framework knowing that the CPU is changing operating point. (There is a similar hack in the s3c MMC driver.)
The clk notifiers are the sane way of doing this.
Yours, Linus Walleij