]> nv-tegra.nvidia Code Review - linux-3.10.git/blobdiff - arch/arm/mach-tegra/cpuidle-t11x.c
arm: tegra: Move driver files to drivers/platform
[linux-3.10.git] / arch / arm / mach-tegra / cpuidle-t11x.c
index 8004518a550e2d4ebc895da3c17413e65cb4da32..fca77209b00b42b468307f710f5f67175022d7d0 100644 (file)
@@ -3,7 +3,7 @@
  *
  * CPU idle driver for Tegra11x CPUs
  *
- * Copyright (c) 2012, NVIDIA Corporation.
+ * Copyright (c) 2012-2014, NVIDIA CORPORATION.  All rights reserved.
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
 #include <linux/clk.h>
 #include <linux/cpu_pm.h>
 #include <linux/module.h>
+#include <linux/tegra-soc.h>
+#include <linux/tegra-timer.h>
+#include <linux/tegra-cpuidle.h>
+#include <linux/irqchip/tegra.h>
 
 #include <asm/cacheflush.h>
-#include <asm/hardware/gic.h>
 #include <asm/localtimer.h>
 #include <asm/suspend.h>
 #include <asm/cputype.h>
+#include <asm/psci.h>
 
-#include <mach/iomap.h>
 #include <mach/irqs.h>
-#include <mach/hardware.h>
 
-#include <trace/events/power.h>
+#include <trace/events/nvpower.h>
 
-#include "clock.h"
-#include "cpuidle.h"
-#include "dvfs.h"
-#include "fuse.h"
-#include "gic.h"
+#include <linux/platform/tegra/clock.h>
+#include <linux/platform/tegra/dvfs.h>
+#include "iomap.h"
 #include "pm.h"
-#include "reset.h"
+#include <linux/platform/tegra/reset.h>
 #include "sleep.h"
-#include "timer.h"
-#include "fuse.h"
+#include <linux/tegra_ptm.h>
 
 #define CLK_RST_CONTROLLER_CPU_CMPLX_STATUS \
        (IO_ADDRESS(TEGRA_CLK_RESET_BASE) + 0x470)
@@ -86,8 +85,14 @@ module_param(slow_cluster_power_gating_noncpu, bool, 0644);
 static uint fast_cluster_power_down_mode __read_mostly;
 module_param(fast_cluster_power_down_mode, uint, 0644);
 
+static bool stop_mc_clk_in_idle __read_mostly = false;
+module_param(stop_mc_clk_in_idle, bool, 0644);
+
 static struct clk *cpu_clk_for_dvfs;
 
+static DEFINE_SPINLOCK(vmin_lock);
+static unsigned long cpu_min_rate;
+
 static int pd_exit_latencies[5];
 
 static struct {
@@ -98,6 +103,7 @@ static struct {
        unsigned long long rail_pd_time;
        unsigned long long c0nc_pg_time;
        unsigned long long c1nc_pg_time;
+       unsigned long long mc_clk_stop_time;
        unsigned int rail_gating_count;
        unsigned int rail_gating_bin[32];
        unsigned int rail_gating_done_count;
@@ -110,8 +116,13 @@ static struct {
        unsigned int c1nc_gating_bin[32];
        unsigned int c1nc_gating_done_count;
        unsigned int c1nc_gating_done_count_bin[32];
+       unsigned int mc_clk_stop_count;
+       unsigned int mc_clk_stop_bin[32];
+       unsigned int mc_clk_stop_done_count;
+       unsigned int mc_clk_stop_done_count_bin[32];
        unsigned int pd_int_count[NR_IRQS];
        unsigned int last_pd_int_count[NR_IRQS];
+       unsigned int clk_gating_vmin;
 } idle_stats;
 
 static inline unsigned int time_to_bin(unsigned int time)
@@ -165,7 +176,9 @@ bool tegra11x_pd_is_allowed(struct cpuidle_device *dev,
                                to_cpumask(&cpu_power_gating_in_idle)))
                return false;
 
-       request = ktime_to_us(tick_nohz_get_sleep_length());
+       if (tegra_cpu_timer_get_remain(&request))
+               return false;
+
        if (state->exit_latency != pd_exit_latencies[cpu_number(dev->cpu)]) {
                /* possible on the 1st entry after cluster switch*/
                state->exit_latency = pd_exit_latencies[cpu_number(dev->cpu)];
@@ -193,7 +206,7 @@ static inline void tegra11_irq_restore_affinity(void)
 #endif
 }
 
-static bool tegra_cpu_cluster_power_down(struct cpuidle_device *dev,
+static int tegra_cpu_cluster_power_down(struct cpuidle_device *dev,
                           struct cpuidle_state *state, s64 request)
 {
        ktime_t entry_time;
@@ -203,6 +216,7 @@ static bool tegra_cpu_cluster_power_down(struct cpuidle_device *dev,
        int bin;
        unsigned int flag = 0;
        s64 sleep_time;
+       int ret = CPUIDLE_STATE_POWERGATING;
 
        /* LP2 entry time */
        entry_time = ktime_get();
@@ -300,6 +314,18 @@ static bool tegra_cpu_cluster_power_down(struct cpuidle_device *dev,
                }
        }
 
+       if (stop_mc_clk_in_idle && (state->power_usage == 0) &&
+           (request > tegra_mc_clk_stop_min_residency())) {
+               flag |= TEGRA_POWER_STOP_MC_CLK;
+
+               trace_nvmc_clk_stop_rcuidle(NVPOWER_MC_CLK_STOP_ENTRY,
+                                               sleep_time);
+               idle_stats.mc_clk_stop_count++;
+               idle_stats.mc_clk_stop_bin[bin]++;
+
+               tegra_mc_clk_prepare();
+       }
+
        if (tegra_idle_power_down_last(sleep_time, flag) == 0)
                sleep_completed = true;
        else {
@@ -309,13 +335,21 @@ static bool tegra_cpu_cluster_power_down(struct cpuidle_device *dev,
 
        clockevents_notify(CLOCK_EVT_NOTIFY_BROADCAST_EXIT, &dev->cpu);
        exit_time = ktime_get();
+
+       if (flag & TEGRA_POWER_STOP_MC_CLK)
+               tegra_mc_clk_finish();
+
        if (!is_lp_cluster())
                tegra_dvfs_rail_on(tegra_cpu_rail, exit_time);
 
-       if (flag == TEGRA_POWER_CLUSTER_PART_CRAIL)
+       if (flag & TEGRA_POWER_STOP_MC_CLK) {
+               idle_stats.mc_clk_stop_time +=
+                       ktime_to_us(ktime_sub(exit_time, entry_time));
+               ret = CPUIDLE_STATE_MC_CLK_STOP;
+       } else if (flag & TEGRA_POWER_CLUSTER_PART_CRAIL)
                idle_stats.rail_pd_time +=
                        ktime_to_us(ktime_sub(exit_time, entry_time));
-       else if (flag == TEGRA_POWER_CLUSTER_PART_NONCPU) {
+       else if (flag & TEGRA_POWER_CLUSTER_PART_NONCPU) {
                if (is_lp_cluster())
                        idle_stats.c1nc_pg_time +=
                                ktime_to_us(ktime_sub(exit_time, entry_time));
@@ -341,10 +375,15 @@ static bool tegra_cpu_cluster_power_down(struct cpuidle_device *dev,
                state->exit_latency = latency;          /* for idle governor */
                smp_wmb();
 
-               if (flag == TEGRA_POWER_CLUSTER_PART_CRAIL) {
+               if (flag & TEGRA_POWER_STOP_MC_CLK) {
+                       trace_nvmc_clk_stop_rcuidle(NVPOWER_MC_CLK_STOP_EXIT,
+                                                       sleep_time);
+                       idle_stats.mc_clk_stop_done_count++;
+                       idle_stats.mc_clk_stop_done_count_bin[bin]++;
+               } else if (flag & TEGRA_POWER_CLUSTER_PART_CRAIL) {
                        idle_stats.rail_gating_done_count++;
                        idle_stats.rail_gating_done_count_bin[bin]++;
-               } else if (flag == TEGRA_POWER_CLUSTER_PART_NONCPU) {
+               } else if (flag & TEGRA_POWER_CLUSTER_PART_NONCPU) {
                        if (is_lp_cluster()) {
                                idle_stats.c1nc_gating_done_count++;
                                idle_stats.c1nc_gating_done_count_bin[bin]++;
@@ -361,54 +400,58 @@ static bool tegra_cpu_cluster_power_down(struct cpuidle_device *dev,
 
        cpu_pm_exit();
 
-       return true;
+       return ret;
+}
+
+static void tegra11x_restore_vmin(void)
+{
+       spin_lock(&vmin_lock);
+
+       if (cpu_min_rate) {
+               idle_stats.clk_gating_vmin++;
+               tegra_cpu_g_idle_rate_exchange(&cpu_min_rate);
+               cpu_min_rate = 0;
+       }
+
+       spin_unlock(&vmin_lock);
 }
 
-static bool tegra_cpu_core_power_down(struct cpuidle_device *dev,
+static int tegra_cpu_core_power_down(struct cpuidle_device *dev,
                           struct cpuidle_state *state, s64 request)
 {
 #ifdef CONFIG_SMP
        s64 sleep_time;
+       u32 cntp_tval;
+       u32 cntfrq;
        ktime_t entry_time;
-       struct arch_timer_context timer_context;
        bool sleep_completed = false;
        struct tick_sched *ts = tick_get_tick_sched(dev->cpu);
-#ifdef CONFIG_TRUSTED_FOUNDATIONS
        unsigned int cpu = cpu_number(dev->cpu);
+#if defined(CONFIG_ARM_PSCI)
+       int psci_ret = -EPERM;
+       unsigned long entry_point = TEGRA_RESET_HANDLER_BASE +
+               tegra_cpu_reset_handler_offset;
+       struct psci_power_state pps = {
+               TEGRA_ID_CPU_SUSPEND_STDBY,
+               PSCI_POWER_STATE_TYPE_POWER_DOWN
+       };
 #endif
-
-       if (!arch_timer_get_state(&timer_context)) {
-               if ((timer_context.cntp_ctl & ARCH_TIMER_CTRL_ENABLE) &&
-                   ~(timer_context.cntp_ctl & ARCH_TIMER_CTRL_IT_MASK)) {
-                       if (timer_context.cntp_tval <= 0) {
-                               cpu_do_idle();
-                               return false;
-                       }
-                       request = div_u64((u64)timer_context.cntp_tval *
-                                       1000000, timer_context.cntfrq);
-#ifdef CONFIG_TEGRA_LP2_CPU_TIMER
-                       if (request >= state->target_residency) {
-                               timer_context.cntp_tval -= state->exit_latency *
-                                       (timer_context.cntfrq / 1000000);
-                               __asm__("mcr p15, 0, %0, c14, c2, 0\n"
-                                       :
-                                       :
-                                       "r"(timer_context.cntp_tval));
-                       }
-#endif
-               }
-       }
-
-       if (!tegra_is_cpu_wake_timer_ready(dev->cpu) ||
-           (request < state->target_residency) ||
-           (!ts) || (ts->nohz_mode == NOHZ_MODE_INACTIVE)) {
+       if ((tegra_cpu_timer_get_remain(&request) == -ETIME) ||
+               (request <= state->target_residency) || (!ts) ||
+               (ts->nohz_mode == NOHZ_MODE_INACTIVE) ||
+               !tegra_is_cpu_wake_timer_ready(dev->cpu)) {
                /*
                 * Not enough time left to enter LP2, or wake timer not ready
                 */
                cpu_do_idle();
-               return false;
+               return CPUIDLE_STATE_CLKGATING;
        }
 
+#ifdef CONFIG_TEGRA_LP2_CPU_TIMER
+       cntfrq = tegra_clk_measure_input_freq();
+       cntp_tval = (request - state->exit_latency) * (cntfrq / 1000000);
+       asm volatile("mcr p15, 0, %0, c14, c2, 0" : : "r"(cntp_tval));
+#endif
        cpu_pm_enter();
 
 #if !defined(CONFIG_TEGRA_LP2_CPU_TIMER)
@@ -416,7 +459,7 @@ static bool tegra_cpu_core_power_down(struct cpuidle_device *dev,
        clockevents_notify(CLOCK_EVT_NOTIFY_BROADCAST_ENTER, &dev->cpu);
        tegra_pd_set_trigger(sleep_time);
 #endif
-       idle_stats.tear_down_count[cpu_number(dev->cpu)]++;
+       idle_stats.tear_down_count[cpu]++;
 
        entry_time = ktime_get();
 
@@ -424,83 +467,161 @@ static bool tegra_cpu_core_power_down(struct cpuidle_device *dev,
        tegra_cpu_wake_by_time[dev->cpu] = ktime_to_us(entry_time) + request;
        smp_wmb();
 
-#ifdef CONFIG_TRUSTED_FOUNDATIONS
+#if defined(CONFIG_ARM_PSCI)
        if ((cpu == 0) || (cpu == 4)) {
-               tegra_generic_smc(0xFFFFFFFC, 0xFFFFFFE7,
-                               (TEGRA_RESET_HANDLER_BASE +
-                               tegra_cpu_reset_handler_offset));
+               if (psci_ops.cpu_suspend) {
+                       psci_ret = psci_ops.cpu_suspend(pps, entry_point);
+                       while (psci_ret == -EPERM)
+                               psci_ret = tegra_restart_prev_smc();
+               }
        }
 #endif
+
        cpu_suspend(0, tegra3_sleep_cpu_secondary_finish);
 
+       tegra11x_restore_vmin();
+
        tegra_cpu_wake_by_time[dev->cpu] = LLONG_MAX;
 
 #ifdef CONFIG_TEGRA_LP2_CPU_TIMER
-       if (!arch_timer_get_state(&timer_context))
-               sleep_completed = (timer_context.cntp_tval <= 0);
+       asm volatile("mrc p15, 0, %0, c14, c2, 0" : "=r" (cntp_tval));
+       if ((s32)cntp_tval <= 0)
+               sleep_completed = true;
 #else
        sleep_completed = !tegra_pd_timer_remain();
        tegra_pd_set_trigger(0);
        clockevents_notify(CLOCK_EVT_NOTIFY_BROADCAST_EXIT, &dev->cpu);
 #endif
        sleep_time = ktime_to_us(ktime_sub(ktime_get(), entry_time));
-       idle_stats.cpu_pg_time[cpu_number(dev->cpu)] += sleep_time;
+       idle_stats.cpu_pg_time[cpu] += sleep_time;
        if (sleep_completed) {
                /*
                 * Stayed in LP2 for the full time until timer expires,
                 * adjust the exit latency based on measurement
                 */
                int offset = sleep_time - request;
-               int latency = pd_exit_latencies[cpu_number(dev->cpu)] +
+               int latency = pd_exit_latencies[cpu] +
                        offset / 16;
                latency = clamp(latency, 0, 10000);
-               pd_exit_latencies[cpu_number(dev->cpu)] = latency;
+               pd_exit_latencies[cpu] = latency;
                state->exit_latency = latency;          /* for idle governor */
                smp_wmb();
        }
 #endif
        cpu_pm_exit();
 
+       return CPUIDLE_STATE_POWERGATING;
+}
+
+static bool tegra11x_idle_enter_vmin(struct cpuidle_device *dev,
+                       struct cpuidle_state *state)
+{
+       int status = -1;
+
+       spin_lock(&vmin_lock);
+
+       cpu_min_rate = 0;
+
+       if (!tegra_rail_off_is_allowed()) {
+               spin_unlock(&vmin_lock);
+               return false;
+       }
+
+       status = tegra_cpu_g_idle_rate_exchange(&cpu_min_rate);
+
+       if (status) {
+               cpu_min_rate = 0;
+               spin_unlock(&vmin_lock);
+               return false;
+       }
+
+       spin_unlock(&vmin_lock);
+
+       cpu_do_idle();
+
+       tegra11x_restore_vmin();
+
        return true;
 }
 
-bool tegra11x_idle_power_down(struct cpuidle_device *dev,
+int tegra11x_idle_power_down(struct cpuidle_device *dev,
                           struct cpuidle_state *state)
 {
-       bool power_down;
+       int power_down = 0;
        bool cpu_gating_only = false;
+       bool clkgt_at_vmin = false;
        bool power_gating_cpu_only = true;
-       s64 request = ktime_to_us(tick_nohz_get_sleep_length());
+       int status = -1;
+       unsigned long rate;
+       s64 request;
+
+       if (tegra_cpu_timer_get_remain(&request)) {
+               cpu_do_idle();
+               return false;
+       }
 
        tegra_set_cpu_in_pd(dev->cpu);
        cpu_gating_only = (((fast_cluster_power_down_mode
                        << TEGRA_POWER_CLUSTER_PART_SHIFT)
-                       & TEGRA_POWER_CLUSTER_PART_MASK) == 0);
+                       & TEGRA_POWER_CLUSTER_PART_MASK) == 0) ||
+                       (tegra_dvfs_is_dfll_bypass() &&
+                        !tegra_dvfs_rail_is_dfll_mode(tegra_cpu_rail));
 
        if (is_lp_cluster()) {
                if (slow_cluster_power_gating_noncpu &&
-                       (request > tegra_min_residency_noncpu()))
+                       (request > tegra_min_residency_ncpu()))
                                power_gating_cpu_only = false;
                else
                        power_gating_cpu_only = true;
-       } else if (!cpu_gating_only &&
-               (num_online_cpus() == 1) &&
-               tegra_rail_off_is_allowed()) {
-               if (fast_cluster_power_down_mode &&
-                       TEGRA_POWER_CLUSTER_FORCE_MASK)
-                       power_gating_cpu_only = cpu_gating_only;
-               else if (request > tegra_min_residency_noncpu())
-                       power_gating_cpu_only = false;
-               else
-                       power_gating_cpu_only = true;
-       } else
-               power_gating_cpu_only = true;
+       } else {
+               if (tegra_dvfs_rail_updating(cpu_clk_for_dvfs))
+                       clkgt_at_vmin = false;
+               else if (tegra_force_clkgt_at_vmin ==
+                               TEGRA_CPUIDLE_FORCE_DO_CLKGT_VMIN)
+                       clkgt_at_vmin = true;
+               else if (tegra_force_clkgt_at_vmin ==
+                               TEGRA_CPUIDLE_FORCE_NO_CLKGT_VMIN)
+                       clkgt_at_vmin = false;
+               else if ((request >= tegra_min_residency_vmin_fmin()) &&
+                        ((request < tegra_min_residency_ncpu()) ||
+                          cpu_gating_only))
+                       clkgt_at_vmin = true;
+
+               if (clkgt_at_vmin)
+                       clkgt_at_vmin = tegra11x_idle_enter_vmin(dev, state);
+
+               if (!clkgt_at_vmin && (num_online_cpus() == 1)) {
+                       if (!cpu_gating_only && tegra_rail_off_is_allowed()) {
+                               if (fast_cluster_power_down_mode &
+                                               TEGRA_POWER_CLUSTER_FORCE_MASK)
+                                       power_gating_cpu_only = false;
+                               else if (request >
+                                               tegra_min_residency_ncpu())
+                                       power_gating_cpu_only = false;
+                               else
+                                       power_gating_cpu_only = true;
+                       } else
+                               power_gating_cpu_only = true;
+               }
+       }
+
+       if (clkgt_at_vmin) {
+               power_down = true;
+       } else if (!power_gating_cpu_only) {
+               if (is_lp_cluster()) {
+                       rate = ULONG_MAX;
+                       status = tegra_cpu_lp_idle_rate_exchange(&rate);
+               }
 
-       if (power_gating_cpu_only)
-               power_down = tegra_cpu_core_power_down(dev, state, request);
-       else
                power_down = tegra_cpu_cluster_power_down(dev, state, request);
 
+               /* restore cpu clock after cluster power ungating */
+               if (status == 0)
+                       tegra_cpu_lp_idle_rate_exchange(&rate);
+       } else
+               power_down = tegra_cpu_core_power_down(dev, state, request);
+
+       ptm_power_idle_resume(dev->cpu);
        tegra_clear_cpu_in_pd(dev->cpu);
 
        return power_down;
@@ -525,6 +646,8 @@ int tegra11x_pd_debug_show(struct seq_file *s, void *data)
                idle_stats.tear_down_count[2],
                idle_stats.tear_down_count[3],
                idle_stats.tear_down_count[4]);
+       seq_printf(s, "clk gating @ Vmin count:      %8u\n",
+               idle_stats.clk_gating_vmin);
        seq_printf(s, "rail gating count:      %8u\n",
                idle_stats.rail_gating_count);
        seq_printf(s, "rail gating completed:  %8u %7u%%\n",
@@ -582,12 +705,16 @@ int tegra11x_pd_debug_show(struct seq_file *s, void *data)
                        idle_stats.cpu_wants_pd_time[4]) : 0));
 
        seq_printf(s, "\n");
-       seq_printf(s, "rail gating time  c0nc gating time  c1nc gating time\n");
-       seq_printf(s, "%8llu ms          %8llu ms          %8llu ms\n",
+       seq_printf(s, "rail gating time  c0nc gating time  " \
+                       "c1nc gating time  mc_clk gating time\n");
+       seq_printf(s, "%8llu ms          %8llu ms          " \
+                       "%8llu ms          %8llu ms\n",
                div64_u64(idle_stats.rail_pd_time, 1000),
                div64_u64(idle_stats.c0nc_pg_time, 1000),
-               div64_u64(idle_stats.c1nc_pg_time, 1000));
-       seq_printf(s, "%8d%%             %8d%%             %8d%%\n",
+               div64_u64(idle_stats.c1nc_pg_time, 1000),
+               div64_u64(idle_stats.mc_clk_stop_time, 1000));
+       seq_printf(s, "%8d%%             %8d%%             " \
+                       "%8d%%             %8d%%\n",
                (int)(idle_stats.cpu_wants_pd_time[0] ?
                        div64_u64(idle_stats.rail_pd_time * 100,
                        idle_stats.cpu_wants_pd_time[0]) : 0),
@@ -596,6 +723,9 @@ int tegra11x_pd_debug_show(struct seq_file *s, void *data)
                        idle_stats.cpu_wants_pd_time[0]) : 0),
                (int)(idle_stats.cpu_wants_pd_time[4] ?
                        div64_u64(idle_stats.c1nc_pg_time * 100,
+                       idle_stats.cpu_wants_pd_time[4]) : 0),
+               (int)(idle_stats.cpu_wants_pd_time[4] ?
+                       div64_u64(idle_stats.mc_clk_stop_time * 100,
                        idle_stats.cpu_wants_pd_time[4]) : 0));
 
        seq_printf(s, "\n");
@@ -640,6 +770,20 @@ int tegra11x_pd_debug_show(struct seq_file *s, void *data)
                        idle_stats.c1nc_gating_done_count_bin[bin] * 100 /
                                idle_stats.c1nc_gating_bin[bin]);
        }
+       seq_printf(s, "\n");
+
+       seq_printf(s, "%19s %8s %8s %8s\n", "", "mc clk stop", "comp", "%");
+       seq_printf(s, "-------------------------------------------------\n");
+       for (bin = 0; bin < 32; bin++) {
+               if (idle_stats.mc_clk_stop_bin[bin] == 0)
+                       continue;
+               seq_printf(s, "%6u - %6u ms: %8u %8u %7u%%\n",
+                       1 << (bin - 1), 1 << bin,
+                       idle_stats.mc_clk_stop_bin[bin],
+                       idle_stats.mc_clk_stop_done_count_bin[bin],
+                       idle_stats.mc_clk_stop_done_count_bin[bin] * 100 /
+                               idle_stats.mc_clk_stop_bin[bin]);
+       }
 
        seq_printf(s, "\n");
        seq_printf(s, "%3s %20s %6s %10s\n",
@@ -673,6 +817,7 @@ int __init tegra11x_cpuidle_init_soc(struct tegra_cpuidle_ops *idle_ops)
 #endif
        };
 
+       cpu_min_rate = 0;
        cpu_clk_for_dvfs = tegra_get_clock_by_name("cpu_g");
 
        for (i = 0; i < ARRAY_SIZE(pd_exit_latencies); i++)