ARM: tegra: clock: Re-factor Tegra3 cpu clocks
Alex Frid [Sun, 13 Mar 2011 08:41:14 +0000 (00:41 -0800)]
Added second level virtualization (on top of virtual cpu rate control)
to support different Tegra3 CPU power modes: low power (LP) mode and
geared performance (G) mode. Virtual cpu complex (cpu_cmplx) clock is
defined as a child with two parents: virtual cpu_lp and virtual cpu_g
clocks for the respective modes. Mode switch sequence was integrated
into cpu_cmplx set parent implementation. (Before this commit mode
switch was triggered outside the clock framework, which created cpu
clock/mode synchronization problems).

Each mode clock is derived from its own super clock mux (cclk_lp and
cclk_g) to statically match Tegra3 h/w layout. (Before this commit the
code had to dynamically synchronize CPU mode and active mux selection).
This change also allowed to support PLLX output divider for low power
mode as fixed 1:2 divider with bypass control embedded into cclk_lp
parent section.

Updated auto and sysfs CPU mode switch calls to use new clock framework,
and removed clock manipulation from the low level mode switch
implementation.

Original-Change-Id: Ibc3cc495b2ff29e2d3417eff2bfd45535cbd015b
Reviewed-on: http://git-master/r/24734
Reviewed-by: Aleksandr Frid <afrid@nvidia.com>
Tested-by: Aleksandr Frid <afrid@nvidia.com>
Tested-by: Jin Qian <jqian@nvidia.com>
Reviewed-by: Scott Williams <scwilliams@nvidia.com>
Original-Change-Id: I23ae80edbf14fb22727a6fc317cd9e5baf8bd6be

Rebase-Id: Rdcd4a2165ebd92bf4caa35d68ca81d19a3789351

arch/arm/mach-tegra/clock.c
arch/arm/mach-tegra/clock.h
arch/arm/mach-tegra/cpu-tegra3.c
arch/arm/mach-tegra/platsmp.c
arch/arm/mach-tegra/pm-t3.c
arch/arm/mach-tegra/pm.h
arch/arm/mach-tegra/sysfs-cluster.c

index e4757c6..2247625 100644 (file)
@@ -115,9 +115,6 @@ static unsigned long clk_predict_rate_from_parent(struct clk *c, struct clk *p)
 
        rate = clk_get_rate(p);
 
-       if (c->ops && c->ops->recalculate_rate)
-               c->ops->recalculate_rate(c);
-
        if (c->mul != 0 && c->div != 0) {
                rate *= c->mul;
                rate += c->div - 1; /* round up */
@@ -127,11 +124,8 @@ static unsigned long clk_predict_rate_from_parent(struct clk *c, struct clk *p)
        return rate;
 }
 
-static unsigned long clk_get_max_rate(struct clk *c)
+unsigned long clk_get_max_rate(struct clk *c)
 {
-       if (c->ops && c->ops->get_max_rate)
-               return c->ops->get_max_rate(c);
-       else
                return c->max_rate;
 }
 
@@ -408,8 +402,6 @@ unsigned long clk_get_rate_all_locked(struct clk *c)
 
        while (p) {
                c = p;
-               if (c->ops && c->ops->recalculate_rate)
-                       c->ops->recalculate_rate(c);
                if (c->mul != 0 && c->div != 0) {
                        mul *= c->mul;
                        div *= c->div;
@@ -584,6 +576,27 @@ static int __init tegra_keep_boot_clocks_setup(char *__unused)
 __setup("tegra_keep_boot_clocks", tegra_keep_boot_clocks_setup);
 
 /*
+ * Bootloader may not match kernel restrictions on CPU clock sources.
+ * Make sure CPU clock is sourced from either main or backup parent.
+ */
+static int tegra_sync_cpu_clock(void)
+{
+       int ret;
+       unsigned long rate;
+       struct clk *c = tegra_get_clock_by_name("cpu");
+
+       BUG_ON(!c);
+       rate = clk_get_rate(c);
+       ret = clk_set_rate(c, rate);
+       if (ret)
+               pr_err("%s: Failed to sync CPU at rate %lu\n", __func__, rate);
+       else
+               pr_info("CPU rate: %lu MHz\n", clk_get_rate(c) / 1000000);
+       return ret;
+}
+late_initcall(tegra_sync_cpu_clock);
+
+/*
  * Iterate through all clocks, disabling any for which the refcount is 0
  * but the clock init detected the bootloader left the clock on.
  */
index 8260cd1..76f1663 100644 (file)
@@ -104,13 +104,16 @@ struct clk_ops {
        int             (*set_parent)(struct clk *, struct clk *);
        int             (*set_rate)(struct clk *, unsigned long);
        long            (*round_rate)(struct clk *, unsigned long);
-       unsigned long   (*get_max_rate)(struct clk *);
-       void            (*recalculate_rate)(struct clk *);
        void            (*reset)(struct clk *, bool);
        int             (*clk_cfg_ex)(struct clk *,
                                enum tegra_clk_ex_param, u32);
 };
 
+enum cpu_mode {
+       MODE_G = 0,
+       MODE_LP,
+};
+
 struct clk {
        /* node for master clocks list */
        struct list_head        node;           /* node for list of all clocks */
@@ -165,7 +168,7 @@ struct clk {
                struct {
                        struct clk                      *main;
                        struct clk                      *backup;
-                       unsigned long                   lp_max_rate;
+                       enum cpu_mode                   mode;
                } cpu;
                struct {
                        struct list_head                node;
@@ -253,6 +256,7 @@ struct tegra_clk_init_table {
 void clk_init(struct clk *clk);
 unsigned long clk_get_rate_locked(struct clk *c);
 void clk_set_cansleep(struct clk *c);
+unsigned long clk_get_max_rate(struct clk *c);
 int clk_set_rate_locked(struct clk *c, unsigned long rate);
 int clk_reparent(struct clk *c, struct clk *parent);
 int tegra_emc_set_rate(unsigned long rate);
index 2d250d0..fdc5331 100644 (file)
 #include <linux/err.h>
 #include <linux/io.h>
 #include <linux/cpu.h>
+#include <linux/clk.h>
 #include <linux/debugfs.h>
 #include <linux/seq_file.h>
 
 #include "pm.h"
+#include "clock.h"
 
 #define INITIAL_STATE          TEGRA_HP_DISABLED
 #define IDLE_HYSTERESIS                100000
@@ -62,6 +64,9 @@ module_param(idle_bottom_freq, uint, 0644);
 
 static unsigned int lpcpu_max_freq;
 
+static struct clk *cpu_clk;
+static struct clk *cpu_g_clk;
+static struct clk *cpu_lp_clk;
 
 static struct {
        cputime64_t time_up_total;
@@ -189,18 +194,20 @@ static void tegra_auto_hotplug_work_func(struct work_struct *work)
                                hotplug_wq, &hotplug_work, down_delay);
                        hp_stats_update(cpu, false);
                } else if (!is_lp_cluster() && !no_lp) {
-                       tegra_cluster_control(0, TEGRA_POWER_CLUSTER_LP |
-                                                TEGRA_POWER_CLUSTER_IMMEDIATE);
-                       hp_stats_update(CONFIG_NR_CPUS, true);
-                       hp_stats_update(0, false);
+                       if(!clk_set_parent(cpu_clk, cpu_lp_clk)) {
+                               hp_stats_update(CONFIG_NR_CPUS, true);
+                               hp_stats_update(0, false);
+                       } else
+                               queue_delayed_work(
+                                       hotplug_wq, &hotplug_work, down_delay);
                }
                break;
        case TEGRA_HP_UP:
                if (is_lp_cluster() && !no_lp) {
-                       tegra_cluster_control(0, TEGRA_POWER_CLUSTER_G |
-                                                TEGRA_POWER_CLUSTER_IMMEDIATE);
-                       hp_stats_update(CONFIG_NR_CPUS, false);
-                       hp_stats_update(0, true);
+                       if(!clk_set_parent(cpu_clk, cpu_g_clk)) {
+                               hp_stats_update(CONFIG_NR_CPUS, false);
+                               hp_stats_update(0, true);
+                       }
                        queue_delayed_work(
                                hotplug_wq, &hotplug_work, up2gn_delay);
                } else {
@@ -281,7 +288,7 @@ int tegra_auto_hotplug_init(void)
 {
        /*
         * Not bound to the issuer CPU (=> high-priority), has rescue worker
-        * task, single-threaded, frrezeable.
+        * task, single-threaded, freezable.
         */
        hotplug_wq = alloc_workqueue(
                "cpu-tegra3", WQ_UNBOUND | WQ_RESCUER | WQ_FREEZEABLE, 1);
@@ -289,7 +296,13 @@ int tegra_auto_hotplug_init(void)
                return -ENOMEM;
        INIT_DELAYED_WORK(&hotplug_work, tegra_auto_hotplug_work_func);
 
-       lpcpu_max_freq = tegra_get_lpcpu_max_rate() / 1000;
+       cpu_clk = clk_get_sys(NULL, "cpu");
+       cpu_g_clk = clk_get_sys(NULL, "cpu_g");
+       cpu_lp_clk = clk_get_sys(NULL, "cpu_lp");
+       if (IS_ERR(cpu_clk) || IS_ERR(cpu_g_clk) || IS_ERR(cpu_lp_clk))
+               return -ENOENT;
+
+       lpcpu_max_freq = clk_get_max_rate(cpu_lp_clk) / 1000;
        idle_top_freq = lpcpu_max_freq;
        idle_bottom_freq = idle_top_freq - IDLE_HYSTERESIS;
 
index 9f3a89a..938234a 100644 (file)
@@ -21,6 +21,7 @@
 #include <linux/jiffies.h>
 #include <linux/smp.h>
 #include <linux/io.h>
+#include <linux/clk.h>
 #include <linux/clk/tegra.h>
 
 #include <asm/smp_scu.h>
@@ -31,6 +32,7 @@
 #include "flowctrl.h"
 #include "reset.h"
 #include "pm.h"
+#include "clock.h"
 
 #include "common.h"
 #include "iomap.h"
@@ -76,16 +78,25 @@ static int tegra20_power_up_cpu(unsigned int cpu)
        int status;
 
        if (is_lp_cluster()) {
+               struct clk *cpu_clk, *cpu_g_clk;
+
                /* The G CPU may not be available for a
                   variety of reasons. */
                status = is_g_cluster_available(cpu);
                if (status)
                        return status;
 
-               /* Switch to the G CPU before continuing. */
-               status = tegra_cluster_control(0,
-                                              TEGRA_POWER_CLUSTER_G |
-                                              TEGRA_POWER_CLUSTER_IMMEDIATE);
+               cpu_clk = tegra_get_clock_by_name("cpu");
+               cpu_g_clk = tegra_get_clock_by_name("cpu_g");
+
+               /* Switch to G CPU before continuing. */
+               if (!cpu_clk || !cpu_g_clk) {
+                       /* Early boot, clock infrastructure is not initialized
+                          - CPU mode switch is not allowed */
+                       status = -EINVAL;
+               } else
+                       status = clk_set_parent(cpu_clk, cpu_g_clk);
+
                if (status)
                        return status;
        }
index f983bf0..b4bf222 100644 (file)
@@ -94,29 +94,32 @@ static int cluster_switch_prolog_clock(unsigned int flags)
        u32 CclkBurstPolicy;
        u32 SuperCclkDivier;
 
-       /* Read the CPU clock settings for the currently active CPU. */
-       CclkBurstPolicy = readl(CAR_CCLK_BURST_POLICY);
-       SuperCclkDivier = readl(CAR_SUPER_CCLK_DIVIDER);
-
        /* Read the bond out register containing the G and LP CPUs. */
        reg = readl(CAR_BOND_OUT_V);
 
+       /* Sync G-PLLX divider bypass with LP (no effect on G, just to prevent
+          LP settings overwrite by save/restore code */
+       CclkBurstPolicy = ~PLLX_DIV2_BYPASS_LP & readl(CAR_CCLKG_BURST_POLICY);
+       CclkBurstPolicy |= PLLX_DIV2_BYPASS_LP & readl(CAR_CCLKLP_BURST_POLICY);
+       writel(CclkBurstPolicy, CAR_CCLKG_BURST_POLICY);
+
        /* Switching to G? */
        if (flags & TEGRA_POWER_CLUSTER_G) {
                /* Do the G CPUs exist? */
                if (reg & CAR_BOND_OUT_V_CPU_G)
                        return -ENXIO;
 
+               /* Keep G CPU clock policy set by upper laayer, with the
+                  exception of the transition via LP1 */
                if (flags & TEGRA_POWER_SDRAM_SELFREFRESH) {
                        /* In LP1 power mode come up on CLKM (oscillator) */
+                       CclkBurstPolicy = readl(CAR_CCLKG_BURST_POLICY);
                        CclkBurstPolicy |= ~0xF;
                        SuperCclkDivier = 0;
-               }
 
-               /* We will be running on the G CPU after the switch.
-                  Set up the G clock policy. */
-               writel(CclkBurstPolicy, CAR_CCLKG_BURST_POLICY);
-               writel(SuperCclkDivier, CAR_SUPER_CCLKG_DIVIDER);
+                       writel(CclkBurstPolicy, CAR_CCLKG_BURST_POLICY);
+                       writel(SuperCclkDivier, CAR_SUPER_CCLKG_DIVIDER);
+               }
 
                /* Hold G CPUs 1-3 in reset after the switch */
                reg = CPU_RESET(1) | CPU_RESET(2) | CPU_RESET(3);
@@ -144,43 +147,17 @@ static int cluster_switch_prolog_clock(unsigned int flags)
                if (reg & CAR_BOND_OUT_V_CPU_LP)
                        return -ENXIO;
 
+               /* Keep LP CPU clock policy set by upper layer, with the
+                  exception of the transition via LP1 */
                if (flags & TEGRA_POWER_SDRAM_SELFREFRESH) {
                        /* In LP1 power mode come up on CLKM (oscillator) */
+                       CclkBurstPolicy = readl(CAR_CCLKLP_BURST_POLICY);
                        CclkBurstPolicy |= ~0xF;
                        SuperCclkDivier = 0;
-               } else {
-                       /* It is possible that PLLX frequency is too high
-                          for the LP CPU. Reduce the frequency if necessary
-                          to prevent over-clocking when we switch. PLLX
-                          has an implied divide-by-2 when the LP CPU is
-                          active unless PLLX_DIV2_BYPASS_LP is selected. */
-
-                       struct clk *c = tegra_get_clock_by_name("cpu");
-                       unsigned long cur_rate = clk_get_rate(c);
-                       unsigned long max_rate = tegra_get_lpcpu_max_rate();
-                       int err;
-
-                       BUG_ON(max_rate == 0);
-                       if (cur_rate/2 > max_rate) {
-                               /* PLLX is running too fast for the LP CPU.
-                                  Reduce it to LP maximum rate which must
-                                  be multipled by 2 because of the LP CPU's
-                                  implied divied-by-2. */
-
-                               DEBUG_CLUSTER(("%s: G freq %lu\r\n", __func__,
-                                              cur_rate));
-                               err = clk_set_rate(c, max_rate * 2);
-                               BUG_ON(err);
-                               DEBUG_CLUSTER(("%s: G freq %lu\r\n", __func__,
-                                              clk_get_rate(c)));
-                       }
-               }
 
-               /* We will be running on the LP CPU after the switch.
-                  Set up the LP clock policy. */
-               CclkBurstPolicy &= ~PLLX_DIV2_BYPASS_LP;
-               writel(CclkBurstPolicy, CAR_CCLKLP_BURST_POLICY);
-               writel(SuperCclkDivier, CAR_SUPER_CCLKLP_DIVIDER);
+                       writel(CclkBurstPolicy, CAR_CCLKLP_BURST_POLICY);
+                       writel(SuperCclkDivier, CAR_SUPER_CCLKLP_DIVIDER);
+               }
 
                /* Take the LP CPU ut of reset after the switch */
                reg = CPU_RESET(0);
@@ -280,7 +257,9 @@ void tegra_cluster_switch_epilog(unsigned int flags)
 
        #if DEBUG_CLUSTER_SWITCH
        {
-               struct clk *c = tegra_get_clock_by_name("cpu");
+               /* FIXME: clock functions below are taking mutex */
+               struct clk *c = tegra_get_clock_by_name(
+                       is_lp_cluster() ? "cpu_lp" : "cpu_g");
                DEBUG_CLUSTER(("%s: %s freq %lu\r\n", __func__,
                        is_lp_cluster() ? "LP" : "G", clk_get_rate(c)));
        }
index 552c2d4..f7522c2 100644 (file)
@@ -93,6 +93,7 @@ void tegra_idle_lp2_last(unsigned int flags);
 #ifdef CONFIG_ARCH_TEGRA_2x_SOC
 #define INSTRUMENT_CLUSTER_SWITCH 0    /* Must be zero for ARCH_TEGRA_2x_SOC */
 #define DEBUG_CLUSTER_SWITCH 0         /* Must be zero for ARCH_TEGRA_2x_SOC */
+#define PARAMETERIZE_CLUSTER_SWITCH 0  /* Must be zero for ARCH_TEGRA_2x_SOC */
 static inline int tegra_cluster_control(unsigned int us, unsigned int flags)
 { return -EPERM; }
 #define tegra_cluster_switch_prolog(flags) do {} while(0)
@@ -101,13 +102,12 @@ static inline bool is_g_cluster_present(void)
 { return true; }
 static inline unsigned int is_lp_cluster(void)
 { return 0; }
-static inline unsigned long tegra_get_lpcpu_max_rate(void)
-{ return 0; }
 #define tegra_lp0_suspend_mc() do {} while(0)
 #define tegra_lp0_resume_mc() do {} while(0)
 #else
 #define INSTRUMENT_CLUSTER_SWITCH 1    /* Should be zero for shipping code */
 #define DEBUG_CLUSTER_SWITCH 1         /* Should be zero for shipping code */
+#define PARAMETERIZE_CLUSTER_SWITCH 1  /* Should be zero for shipping code */
 int tegra_cluster_control(unsigned int us, unsigned int flags);
 void tegra_cluster_switch_prolog(unsigned int flags);
 void tegra_cluster_switch_epilog(unsigned int flags);
@@ -124,7 +124,6 @@ static inline unsigned int is_lp_cluster(void)
        reg = readl(FLOW_CTRL_CLUSTER_CONTROL);
        return (reg & 1); /* 0 == G, 1 == LP*/
 }
-unsigned long tegra_get_lpcpu_max_rate(void);
 void tegra_lp0_suspend_mc(void);
 void tegra_lp0_resume_mc(void);
 #endif
@@ -135,5 +134,12 @@ extern unsigned int tegra_cluster_debug;
 #else
 #define DEBUG_CLUSTER(x) do { } while (0)
 #endif
+#if PARAMETERIZE_CLUSTER_SWITCH
+void tegra_cluster_switch_set_parameters(unsigned int us, unsigned int flags);
+#else
+static inline void tegra_cluster_switch_set_parameters(
+       unsigned int us, unsigned int flags)
+{ }
+#endif
 
 #endif /* _MACH_TEGRA_SUSPEND_H_ */
index 60a11bc..7a144ee 100644 (file)
 #include <linux/kobject.h>
 #include <linux/smp.h>
 #include <linux/io.h>
+#include <linux/clk.h>
 
 #include "iomap.h"
 #include "power.h"
+#include "clock.h"
 
 #define SYSFS_CLUSTER_PRINTS      1    /* Nonzero: enable status prints */
 #define SYSFS_CLUSTER_TRACE_PRINTS 0   /* Nonzero: enable trace prints */
@@ -239,6 +241,15 @@ static ssize_t sysfscluster_store(struct kobject *kobj,
        int e;
        int tmp;
        int cnt;
+       struct clk *cpu_clk = tegra_get_clock_by_name("cpu");
+       struct clk *cpu_g_clk = tegra_get_clock_by_name("cpu_g");
+       struct clk *cpu_lp_clk = tegra_get_clock_by_name("cpu_lp");
+       struct clk *new_parent = NULL;
+
+       if (!cpu_clk || !cpu_g_clk || !cpu_lp_clk) {
+               ret = -ENOSYS;
+               goto fail;
+       }
 
        TRACE_CLUSTER(("+sysfscluster_store: %p, %d\n", buf, count));
 
@@ -282,12 +293,9 @@ static ssize_t sysfscluster_store(struct kobject *kobj,
                        request |= TEGRA_POWER_SDRAM_SELFREFRESH;
                }
 #endif
-               e = tegra_cluster_control(wake_ms * 1000, request);
-               if (e) {
-                       PRINT_CLUSTER(("cluster/active: request failed (%d)\n",
-                                      e));
-                       ret = e;
-               }
+               tegra_cluster_switch_set_parameters(wake_ms * 1000, request);
+               new_parent = (flags & TEGRA_POWER_CLUSTER_LP) ?
+                       cpu_lp_clk : cpu_g_clk;
                break;
 
        case ClusterAttr_Immediate:
@@ -372,6 +380,14 @@ static ssize_t sysfscluster_store(struct kobject *kobj,
 
        spin_unlock(&cluster_lock);
 
+       if (new_parent) {
+               e = clk_set_parent(cpu_clk, new_parent);
+               if (e) {
+                       PRINT_CLUSTER(("cluster/active: request failed (%d)\n",
+                                      e));
+                       ret = e;
+               }
+       }
 fail:
        TRACE_CLUSTER(("-sysfscluster_store: %d\n", count));
        return ret;