summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--arch/arm/mach-tegra/Kconfig5
-rw-r--r--arch/arm/mach-tegra/cpuidle-t3.c146
-rw-r--r--arch/arm/mach-tegra/pm.h8
-rw-r--r--arch/arm/mach-tegra/timer-t3.c17
4 files changed, 116 insertions, 60 deletions
diff --git a/arch/arm/mach-tegra/Kconfig b/arch/arm/mach-tegra/Kconfig
index 0a5d4d930790..2f9205687c88 100644
--- a/arch/arm/mach-tegra/Kconfig
+++ b/arch/arm/mach-tegra/Kconfig
@@ -41,7 +41,7 @@ config ARCH_TEGRA_3x_SOC
select ARCH_SUPPORTS_MSI if TEGRA_PCI
select PCI_MSI if TEGRA_PCI
select ARM_ERRATA_754322
- select TEGRA_LP2_ARM_TWD if HAVE_ARM_TWD
+ select TEGRA_LP2_ARM_TWD if HAVE_ARM_TWD && !TEGRA_RAIL_OFF_MULTIPLE_CPUS
help
Support for NVIDIA Tegra 3 family of SoCs, based upon the
ARM CortexA9MP CPU and the ARM PL310 L2 cache controller
@@ -475,6 +475,9 @@ config TEGRA_WDT_RECOVERY
config TEGRA_LP2_ARM_TWD
bool
+
+config TEGRA_RAIL_OFF_MULTIPLE_CPUS
+ bool
endif
config TEGRA_SLOW_CSITE
diff --git a/arch/arm/mach-tegra/cpuidle-t3.c b/arch/arm/mach-tegra/cpuidle-t3.c
index 4cdfbf2abbc0..c132107a718e 100644
--- a/arch/arm/mach-tegra/cpuidle-t3.c
+++ b/arch/arm/mach-tegra/cpuidle-t3.c
@@ -61,6 +61,8 @@
#define CLK_RST_CONTROLLER_CPU_CMPLX_STATUS \
(IO_ADDRESS(TEGRA_CLK_RESET_BASE) + 0x470)
+#define PMC_POWERGATE_STATUS \
+ (IO_ADDRESS(TEGRA_PMC_BASE) + 0x038)
#ifdef CONFIG_SMP
static s64 tegra_cpu_wake_by_time[4] = {
@@ -117,6 +119,22 @@ void tegra3_cpu_idle_stats_lp2_time(unsigned int cpu, s64 us)
idle_stats.cpu_wants_lp2_time[cpu_number(cpu)] += us;
}
+/* Allow rail off only if all secondary CPUs are power gated, and no
+ rail update is in progress */
+static bool tegra3_rail_off_is_allowed(void)
+{
+ u32 rst = readl(CLK_RST_CONTROLLER_CPU_CMPLX_STATUS);
+ u32 pg = readl(PMC_POWERGATE_STATUS) >> 8;
+
+ if (((rst & 0xE) != 0xE) || ((pg & 0xE) != 0))
+ return false;
+
+ if (tegra_dvfs_rail_updating(cpu_clk_for_dvfs))
+ return false;
+
+ return true;
+}
+
bool tegra3_lp2_is_allowed(struct cpuidle_device *dev,
struct cpuidle_state *state)
{
@@ -135,20 +153,15 @@ bool tegra3_lp2_is_allowed(struct cpuidle_device *dev,
num_online_cpus() > 1)
return false;
+#ifndef CONFIG_TEGRA_RAIL_OFF_MULTIPLE_CPUS
/* FIXME: All CPU's entering LP2 is not working.
* Don't let CPU0 enter LP2 when any secondary CPU is online.
*/
if ((dev->cpu == 0) && (num_online_cpus() > 1))
return false;
-
- if (dev->cpu == 0) {
- u32 reg = readl(CLK_RST_CONTROLLER_CPU_CMPLX_STATUS);
- if ((reg & 0xE) != 0xE)
- return false;
-
- if (tegra_dvfs_rail_updating(cpu_clk_for_dvfs))
- return false;
- }
+#endif
+ if ((dev->cpu == 0) && (!tegra3_rail_off_is_allowed()))
+ return false;
request = ktime_to_us(tick_nohz_get_sleep_length());
if (state->exit_latency != lp2_exit_latencies[cpu_number(dev->cpu)]) {
@@ -171,13 +184,29 @@ static inline void tegra3_lp3_fall_back(struct cpuidle_device *dev)
dev->last_state = &dev->states[0];
}
+static inline void tegra3_lp2_restore_affinity(void)
+{
+#ifdef CONFIG_SMP
+ /* Disable the distributor. */
+ tegra_gic_dist_disable();
+
+ /* Restore the other CPU's interrupt affinity. */
+ tegra_gic_restore_affinity();
+
+ /* Re-enable the distributor. */
+ tegra_gic_dist_enable();
+#endif
+}
+
static void tegra3_idle_enter_lp2_cpu_0(struct cpuidle_device *dev,
struct cpuidle_state *state, s64 request)
{
ktime_t entry_time;
ktime_t exit_time;
bool sleep_completed = false;
+ bool multi_cpu_entry = false;
int bin;
+ s64 sleep_time;
/* LP2 entry time */
entry_time = ktime_get();
@@ -189,7 +218,8 @@ static void tegra3_idle_enter_lp2_cpu_0(struct cpuidle_device *dev,
}
#ifdef CONFIG_SMP
- if (!is_lp_cluster() && (num_online_cpus() > 1)) {
+ multi_cpu_entry = !is_lp_cluster() && (num_online_cpus() > 1);
+ if (multi_cpu_entry) {
s64 wake_time;
unsigned int i;
@@ -201,20 +231,13 @@ static void tegra3_idle_enter_lp2_cpu_0(struct cpuidle_device *dev,
/* Did an interrupt come in for another CPU before we
could disable the distributor? */
- if (!tegra3_lp2_is_allowed(dev, state)) {
+ if (!tegra3_rail_off_is_allowed()) {
/* Yes, re-enable the distributor and LP3. */
tegra_gic_dist_enable();
tegra3_lp3_fall_back(dev);
return;
}
- /* Save and disable the affinity setting for the other
- CPUs and route all interrupts to CPU0. */
- tegra_gic_disable_affinity();
-
- /* Re-enable the distributor. */
- tegra_gic_dist_enable();
-
/* LP2 initial targeted wake time */
wake_time = ktime_to_us(entry_time) + request;
@@ -227,54 +250,56 @@ static void tegra3_idle_enter_lp2_cpu_0(struct cpuidle_device *dev,
/* LP2 actual targeted wake time */
request = wake_time - ktime_to_us(entry_time);
BUG_ON(wake_time < 0LL);
- }
-#endif
- if (request > state->target_residency) {
- s64 sleep_time = request -
- lp2_exit_latencies[cpu_number(dev->cpu)];
-
- bin = time_to_bin((u32)request / 1000);
- idle_stats.tear_down_count[cpu_number(dev->cpu)]++;
- idle_stats.lp2_count++;
- idle_stats.lp2_count_bin[bin]++;
-
- trace_power_start(POWER_CSTATE, 2, dev->cpu);
- clockevents_notify(CLOCK_EVT_NOTIFY_BROADCAST_ENTER, &dev->cpu);
- if (!is_lp_cluster())
- tegra_dvfs_rail_off(tegra_cpu_rail, entry_time);
-
- if (tegra_idle_lp2_last(sleep_time, 0) == 0)
- sleep_completed = true;
- else {
- int irq = tegra_gic_pending_interrupt();
- idle_stats.lp2_int_count[irq]++;
+ if (request < state->target_residency) {
+ /* Not enough time left to enter LP2 */
+ tegra_gic_dist_enable();
+ tegra3_lp3_fall_back(dev);
+ return;
}
- clockevents_notify(CLOCK_EVT_NOTIFY_BROADCAST_EXIT, &dev->cpu);
- exit_time = ktime_get();
- if (!is_lp_cluster())
- tegra_dvfs_rail_on(tegra_cpu_rail, exit_time);
- idle_stats.in_lp2_time[cpu_number(dev->cpu)] +=
- ktime_to_us(ktime_sub(exit_time, entry_time));
- } else
- exit_time = ktime_get();
-
-
-#ifdef CONFIG_SMP
- if (!is_lp_cluster() && (num_online_cpus() > 1)) {
+ /* Cancel LP2 wake timers for all secondary CPUs */
+ tegra_lp2_timer_cancel_secondary();
- /* Disable the distributor. */
- tegra_gic_dist_disable();
-
- /* Restore the other CPU's interrupt affinity. */
- tegra_gic_restore_affinity();
+ /* Save and disable the affinity setting for the other
+ CPUs and route all interrupts to CPU0. */
+ tegra_gic_disable_affinity();
/* Re-enable the distributor. */
tegra_gic_dist_enable();
}
#endif
+ sleep_time = request -
+ lp2_exit_latencies[cpu_number(dev->cpu)];
+
+ bin = time_to_bin((u32)request / 1000);
+ idle_stats.tear_down_count[cpu_number(dev->cpu)]++;
+ idle_stats.lp2_count++;
+ idle_stats.lp2_count_bin[bin]++;
+
+ trace_power_start(POWER_CSTATE, 2, dev->cpu);
+ clockevents_notify(CLOCK_EVT_NOTIFY_BROADCAST_ENTER, &dev->cpu);
+ if (!is_lp_cluster())
+ tegra_dvfs_rail_off(tegra_cpu_rail, entry_time);
+
+ if (tegra_idle_lp2_last(sleep_time, 0) == 0)
+ sleep_completed = true;
+ else {
+ int irq = tegra_gic_pending_interrupt();
+ idle_stats.lp2_int_count[irq]++;
+ }
+
+ clockevents_notify(CLOCK_EVT_NOTIFY_BROADCAST_EXIT, &dev->cpu);
+ exit_time = ktime_get();
+ if (!is_lp_cluster())
+ tegra_dvfs_rail_on(tegra_cpu_rail, exit_time);
+ idle_stats.in_lp2_time[cpu_number(dev->cpu)] +=
+ ktime_to_us(ktime_sub(exit_time, entry_time));
+
+ if (multi_cpu_entry)
+ tegra3_lp2_restore_affinity();
+
if (sleep_completed) {
/*
* Stayed in LP2 for the full time until the next tick,
@@ -389,9 +414,12 @@ void tegra3_idle_lp2(struct cpuidle_device *dev,
cpu_pm_enter();
- if (last_cpu && (dev->cpu == 0))
- tegra3_idle_enter_lp2_cpu_0(dev, state, request);
- else
+ if (dev->cpu == 0) {
+ if (last_cpu)
+ tegra3_idle_enter_lp2_cpu_0(dev, state, request);
+ else
+ tegra3_lp3_fall_back(dev);
+ } else
tegra3_idle_enter_lp2_cpu_n(dev, state, request);
cpu_pm_exit();
diff --git a/arch/arm/mach-tegra/pm.h b/arch/arm/mach-tegra/pm.h
index 44320c891495..2b89e7cb05b5 100644
--- a/arch/arm/mach-tegra/pm.h
+++ b/arch/arm/mach-tegra/pm.h
@@ -153,6 +153,7 @@ unsigned long tegra2_lp2_timer_remain(void);
void tegra3_lp2_set_trigger(unsigned long cycles);
unsigned long tegra3_lp2_timer_remain(void);
int tegra3_is_lp2_timer_ready(unsigned int cpu);
+void tegra3_lp2_timer_cancel_secondary(void);
#endif
static inline void tegra_lp0_suspend_init(void)
@@ -191,6 +192,13 @@ static inline int tegra_is_lp2_timer_ready(unsigned int cpu)
#endif
}
+static inline void tegra_lp2_timer_cancel_secondary(void)
+{
+#ifndef CONFIG_ARCH_TEGRA_2x_SOC
+ tegra3_lp2_timer_cancel_secondary();
+#endif
+}
+
#if DEBUG_CLUSTER_SWITCH && 0 /* !!!FIXME!!! THIS IS BROKEN */
extern unsigned int tegra_cluster_debug;
#define DEBUG_CLUSTER(x) do { if (tegra_cluster_debug) printk x; } while (0)
diff --git a/arch/arm/mach-tegra/timer-t3.c b/arch/arm/mach-tegra/timer-t3.c
index 15c607dfbea7..7b24e7cafcbb 100644
--- a/arch/arm/mach-tegra/timer-t3.c
+++ b/arch/arm/mach-tegra/timer-t3.c
@@ -70,6 +70,7 @@
static void __iomem *timer_reg_base = IO_ADDRESS(TEGRA_TMR1_BASE);
static cpumask_t wake_timer_ready;
+static cpumask_t wake_timer_canceled;
#define timer_writel(value, reg) \
__raw_writel(value, (u32)timer_reg_base + (reg))
@@ -217,6 +218,9 @@ unsigned long tegra3_lp2_timer_remain(void)
{
int cpu = cpu_number();
+ if (cpumask_test_and_clear_cpu(cpu, &wake_timer_canceled))
+ return -ETIME;
+
return timer_readl(lp2_wake_timers[cpu] + TIMER_PCR) & 0x1ffffffful;
}
@@ -224,6 +228,19 @@ int tegra3_is_lp2_timer_ready(unsigned int cpu)
{
return cpumask_test_cpu(cpu, &wake_timer_ready);
}
+
+void tegra3_lp2_timer_cancel_secondary(void)
+{
+ int cpu;
+ int base;
+
+ for (cpu = 1; cpu < ARRAY_SIZE(lp2_wake_timers); cpu++) {
+ base = lp2_wake_timers[cpu];
+ cpumask_set_cpu(cpu, &wake_timer_canceled);
+ timer_writel(0, base + TIMER_PTV);
+ timer_writel(1<<30, base + TIMER_PCR);
+ }
+}
#endif
void __init tegra3_init_timer(u32 *offset, int *irq)