#include <linux/tick.h>
#include <asm/cacheflush.h>
+#include <asm/hardware/gic.h>
#include <asm/localtimer.h>
#include <mach/iomap.h>
static bool lp2_disabled_by_suspend;
module_param(lp2_in_idle, bool, 0644);
-static s64 tegra_cpu1_idle_time;
+static s64 tegra_cpu1_idle_time = LLONG_MAX;;
static int tegra_lp2_exit_latency;
static int tegra_lp2_power_off_time;
unsigned int lp2_completed_count;
unsigned int lp2_count_bin[32];
unsigned int lp2_completed_count_bin[32];
+ unsigned int lp2_int_count[NR_IRQS];
+ unsigned int last_lp2_int_count[NR_IRQS];
} idle_stats;
struct cpuidle_driver tegra_idle = {
static inline unsigned int time_to_bin(unsigned int time)
{
- unsigned int bin = 0;
- int i;
-
- for (i = 4; i >= 0; i--) {
- if (time > (1 << (1 << i)) - 1) {
- time >>= (1 << i);
- bin += (1 << i);
- }
- }
-
- return bin;
+ return fls(time);
}
static inline void tegra_unmask_irq(int irq)
flow_ctrl = flow_ctrl + FLOW_CTRL_HALT_CPUx_EVENTS(dev->cpu);
+ stop_critical_timings();
dsb();
__raw_writel(reg, flow_ctrl);
reg = __raw_readl(flow_ctrl);
__asm__ volatile ("wfi");
__raw_writel(0, flow_ctrl);
reg = __raw_readl(flow_ctrl);
+ start_critical_timings();
}
+#ifdef CONFIG_SMP
static inline bool tegra_wait_for_both_idle(struct cpuidle_device *dev)
{
int wake_int;
return !!(readl(CLK_RST_CONTROLLER_RST_CPU_CMPLX_SET) & (1 << cpu));
}
+static int tegra_tear_down_cpu1(void)
+{
+ u32 reg;
+
+ /* Signal to CPU1 to tear down */
+ tegra_legacy_force_irq_set(TEGRA_CPUIDLE_TEAR_DOWN);
+
+ /* At this point, CPU0 can no longer abort LP2, but CP1 can */
+ /* TODO: any way not to poll here? Use the LP2 timer to wfi? */
+ /* takes ~80 us */
+ while (!tegra_cpu_in_reset(1) &&
+ tegra_legacy_force_irq_status(TEGRA_CPUIDLE_BOTH_IDLE))
+ cpu_relax();
+
+ tegra_legacy_force_irq_clr(TEGRA_CPUIDLE_TEAR_DOWN);
+
+ /* If CPU1 aborted LP2, restart the process */
+ if (!tegra_legacy_force_irq_status(TEGRA_CPUIDLE_BOTH_IDLE))
+ return -EAGAIN;
+
+ /* CPU1 is ready for LP2, clock gate it */
+ reg = readl(CLK_RST_CONTROLLER_CLK_CPU_CMPLX);
+ writel(reg | (1<<9), CLK_RST_CONTROLLER_CLK_CPU_CMPLX);
+
+ return 0;
+}
+
+static void tegra_wake_cpu1(void)
+{
+ unsigned long boot_vector;
+ unsigned long old_boot_vector;
+ unsigned long timeout;
+ u32 reg;
+
+ boot_vector = virt_to_phys(tegra_hotplug_startup);
+ old_boot_vector = readl(EVP_CPU_RESET_VECTOR);
+ writel(boot_vector, EVP_CPU_RESET_VECTOR);
+
+ /* enable cpu clock on cpu */
+ reg = readl(CLK_RST_CONTROLLER_CLK_CPU_CMPLX);
+ writel(reg & ~(1 << (8 + 1)), CLK_RST_CONTROLLER_CLK_CPU_CMPLX);
+
+ reg = 0x1111 << 1;
+ writel(reg, CLK_RST_CONTROLLER_RST_CPU_CMPLX_CLR);
+
+ /* unhalt the cpu */
+ writel(0, IO_ADDRESS(TEGRA_FLOW_CTRL_BASE) + 0x14);
+
+ timeout = jiffies + msecs_to_jiffies(1000);
+ while (time_before(jiffies, timeout)) {
+ if (readl(EVP_CPU_RESET_VECTOR) != boot_vector)
+ break;
+ udelay(10);
+ }
+
+ /* put the old boot vector back */
+ writel(old_boot_vector, EVP_CPU_RESET_VECTOR);
+
+ /* CPU1 is now started */
+}
+#else
+static inline bool tegra_wait_for_both_idle(struct cpuidle_device *dev)
+{
+ return true;
+}
+
+static inline int tegra_tear_down_cpu1(void)
+{
+ return 0;
+}
+
+static inline void tegra_wake_cpu1(void)
+{
+}
+#endif
+
static void tegra_idle_enter_lp2_cpu0(struct cpuidle_device *dev,
struct cpuidle_state *state)
{
s64 request;
- u32 reg;
ktime_t enter;
ktime_t exit;
bool sleep_completed = false;
int bin;
- unsigned long boot_vector;
- unsigned long old_boot_vector;
- unsigned long timeout;
restart:
if (!tegra_wait_for_both_idle(dev))
/* CPU1 woke CPU0 because both are idle */
request = ktime_to_us(tick_nohz_get_sleep_length());
- if (request < tegra_lp2_exit_latency) {
+ if (request < state->target_residency) {
/* Not enough time left to enter LP2 */
tegra_flow_wfi(dev);
return;
}
- /* Signal to CPU1 to tear down */
- tegra_legacy_force_irq_set(TEGRA_CPUIDLE_TEAR_DOWN);
-
- /* At this point, CPU0 can no longer abort LP2, but CP1 can */
- /* TODO: any way not to poll here? Use the LP2 timer to wfi? */
- /* takes ~80 us */
- while (!tegra_cpu_in_reset(1) &&
- tegra_legacy_force_irq_status(TEGRA_CPUIDLE_BOTH_IDLE))
- cpu_relax();
-
- tegra_legacy_force_irq_clr(TEGRA_CPUIDLE_TEAR_DOWN);
-
idle_stats.tear_down_count++;
- /* If CPU1 aborted LP2, restart the process */
- if (!tegra_legacy_force_irq_status(TEGRA_CPUIDLE_BOTH_IDLE))
+ if (tegra_tear_down_cpu1())
goto restart;
- /* CPU1 is ready for LP2, clock gate it */
- reg = readl(CLK_RST_CONTROLLER_CLK_CPU_CMPLX);
- writel(reg | (1<<9), CLK_RST_CONTROLLER_CLK_CPU_CMPLX);
-
/* Enter LP2 */
request = ktime_to_us(tick_nohz_get_sleep_length());
smp_rmb();
request = min_t(s64, request, tegra_cpu1_idle_time);
enter = ktime_get();
- if (request > tegra_lp2_exit_latency + state->target_residency) {
+ if (request > state->target_residency) {
s64 sleep_time = request - tegra_lp2_exit_latency;
bin = time_to_bin((u32)request / 1000);
if (tegra_suspend_lp2(sleep_time) == 0)
sleep_completed = true;
+ else
+ idle_stats.lp2_int_count[tegra_pending_interrupt()]++;
}
/* Bring CPU1 out of LP2 */
/* set the reset vector to point to the secondary_startup routine */
smp_wmb();
- boot_vector = virt_to_phys(tegra_hotplug_startup);
- old_boot_vector = readl(EVP_CPU_RESET_VECTOR);
- writel(boot_vector, EVP_CPU_RESET_VECTOR);
-
- /* enable cpu clock on cpu */
- reg = readl(CLK_RST_CONTROLLER_CLK_CPU_CMPLX);
- writel(reg & ~(1 << (8 + 1)), CLK_RST_CONTROLLER_CLK_CPU_CMPLX);
-
- reg = 0x1111 << 1;
- writel(reg, CLK_RST_CONTROLLER_RST_CPU_CMPLX_CLR);
- /* unhalt the cpu */
- writel(0, IO_ADDRESS(TEGRA_FLOW_CTRL_BASE) + 0x14);
-
- timeout = jiffies + msecs_to_jiffies(1000);
- while (time_before(jiffies, timeout)) {
- if (readl(EVP_CPU_RESET_VECTOR) != boot_vector)
- break;
- udelay(10);
- }
-
- /* put the old boot vector back */
- writel(old_boot_vector, EVP_CPU_RESET_VECTOR);
-
- /* CPU1 is now started */
+ tegra_wake_cpu1();
/*
* TODO: is it worth going back to wfi if no interrupt is pending
}
}
+#ifdef CONFIG_SMP
static void tegra_idle_enter_lp2_cpu1(struct cpuidle_device *dev,
struct cpuidle_state *state)
{
/* Prepare CPU1 for LP2 by putting it in reset */
+ stop_critical_timings();
gic_cpu_exit(0);
barrier();
twd_ctrl = readl(twd_base + 0x8);
tegra_legacy_force_irq_clr(TEGRA_CPUIDLE_BOTH_IDLE);
writel(smp_processor_id(), EVP_CPU_RESET_VECTOR);
+ start_critical_timings();
/*
* TODO: is it worth going back to wfi if no interrupt is pending
out:
tegra_legacy_force_irq_clr(TEGRA_CPUIDLE_BOTH_IDLE);
}
+#endif
static int tegra_idle_enter_lp3(struct cpuidle_device *dev,
struct cpuidle_state *state)
idle_stats.cpu_ready_count[dev->cpu]++;
+#ifdef CONFIG_SMP
if (dev->cpu == 0)
tegra_idle_enter_lp2_cpu0(dev, state);
else
tegra_idle_enter_lp2_cpu1(dev, state);
+#else
+ tegra_idle_enter_lp2_cpu0(dev, state);
+#endif
exit = ktime_sub(ktime_get(), enter);
us = ktime_to_us(exit);
return (int)us;
}
-static int tegra_idle_enter(unsigned int cpu)
+static int tegra_cpuidle_register_device(unsigned int cpu)
{
struct cpuidle_device *dev;
struct cpuidle_state *state;
state->flags = CPUIDLE_FLAG_BALANCED | CPUIDLE_FLAG_TIME_VALID;
state->enter = tegra_idle_enter_lp2;
+ dev->power_specified = 1;
dev->safe_state = state;
dev->state_count++;
return ret;
for_each_possible_cpu(cpu) {
- if (tegra_idle_enter(cpu))
+ if (tegra_cpuidle_register_device(cpu))
pr_err("CPU%u: error initializing idle loop\n", cpu);
}
static int tegra_lp2_debug_show(struct seq_file *s, void *data)
{
int bin;
+ int i;
seq_printf(s, " cpu0 cpu1\n");
seq_printf(s, "-------------------------------------------------\n");
seq_printf(s, "cpu ready: %8u %8u\n",
idle_stats.lp2_count_bin[bin]);
}
+ seq_printf(s, "\n");
+ seq_printf(s, "%3s %20s %6s %10s\n",
+ "int", "name", "count", "last count");
+ seq_printf(s, "--------------------------------------------\n");
+ for (i = 0; i < NR_IRQS; i++) {
+ if (idle_stats.lp2_int_count[i] == 0)
+ continue;
+ seq_printf(s, "%3d %20s %6d %10d\n",
+ i, irq_to_desc(i)->action ?
+ irq_to_desc(i)->action->name ?: "???" : "???",
+ idle_stats.lp2_int_count[i],
+ idle_stats.lp2_int_count[i] -
+ idle_stats.last_lp2_int_count[i]);
+ idle_stats.last_lp2_int_count[i] = idle_stats.lp2_int_count[i];
+ };
return 0;
}