Skip to content

Commit

Permalink
---
Browse files Browse the repository at this point in the history
yaml
---
r: 340060
b: refs/heads/master
c: d552920
h: refs/heads/master
v: v3
  • Loading branch information
Joseph Lo authored and Stephen Warren committed Nov 15, 2012
1 parent 18bc316 commit e96794c
Show file tree
Hide file tree
Showing 7 changed files with 276 additions and 5 deletions.
2 changes: 1 addition & 1 deletion [refs]
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
---
refs/heads/master: 01459c69dd48badeb7833c3293e43f7b8ae75e31
refs/heads/master: d552920a02759cdc45d8507868de10ac2f5b9a18
44 changes: 40 additions & 4 deletions trunk/arch/arm/mach-tegra/cpuidle-tegra30.c
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@

#include "pm.h"
#include "sleep.h"
#include "tegra_cpu_car.h"

#ifdef CONFIG_PM_SLEEP
static int tegra30_idle_lp2(struct cpuidle_device *dev,
Expand Down Expand Up @@ -67,6 +68,31 @@ static struct cpuidle_driver tegra_idle_driver = {
static DEFINE_PER_CPU(struct cpuidle_device, tegra_idle_device);

#ifdef CONFIG_PM_SLEEP
static bool tegra30_cpu_cluster_power_down(struct cpuidle_device *dev,
struct cpuidle_driver *drv,
int index)
{
struct cpuidle_state *state = &drv->states[index];
u32 cpu_on_time = state->exit_latency;
u32 cpu_off_time = state->target_residency - state->exit_latency;

/* All CPUs entering LP2 is not working.
* Don't let CPU0 enter LP2 when any secondary CPU is online.
*/
if (num_online_cpus() > 1 || !tegra_cpu_rail_off_ready()) {
cpu_do_idle();
return false;
}

clockevents_notify(CLOCK_EVT_NOTIFY_BROADCAST_ENTER, &dev->cpu);

tegra_idle_lp2_last(cpu_on_time, cpu_off_time);

clockevents_notify(CLOCK_EVT_NOTIFY_BROADCAST_EXIT, &dev->cpu);

return true;
}

#ifdef CONFIG_SMP
static bool tegra30_cpu_core_power_down(struct cpuidle_device *dev,
struct cpuidle_driver *drv,
Expand Down Expand Up @@ -101,16 +127,22 @@ static int __cpuinit tegra30_idle_lp2(struct cpuidle_device *dev,
{
u32 cpu = is_smp() ? cpu_logical_map(dev->cpu) : dev->cpu;
bool entered_lp2 = false;
bool last_cpu;

local_fiq_disable();

tegra_set_cpu_in_lp2(cpu);
last_cpu = tegra_set_cpu_in_lp2(cpu);
cpu_pm_enter();

if (cpu == 0)
cpu_do_idle();
else
if (cpu == 0) {
if (last_cpu)
entered_lp2 = tegra30_cpu_cluster_power_down(dev, drv,
index);
else
cpu_do_idle();
} else {
entered_lp2 = tegra30_cpu_core_power_down(dev, drv, index);
}

cpu_pm_exit();
tegra_clear_cpu_in_lp2(cpu);
Expand All @@ -130,6 +162,10 @@ int __init tegra30_cpuidle_init(void)
struct cpuidle_device *dev;
struct cpuidle_driver *drv = &tegra_idle_driver;

#ifdef CONFIG_PM_SLEEP
tegra_tear_down_cpu = tegra30_tear_down_cpu;
#endif

ret = cpuidle_register_driver(&tegra_idle_driver);
if (ret) {
pr_err("CPUidle driver registration failed\n");
Expand Down
144 changes: 144 additions & 0 deletions trunk/arch/arm/mach-tegra/pm.c
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,36 @@
#include <linux/spinlock.h>
#include <linux/io.h>
#include <linux/cpumask.h>
#include <linux/delay.h>
#include <linux/cpu_pm.h>
#include <linux/clk.h>
#include <linux/err.h>

#include <asm/smp_plat.h>
#include <asm/cacheflush.h>
#include <asm/suspend.h>
#include <asm/idmap.h>
#include <asm/proc-fns.h>
#include <asm/tlbflush.h>

#include "iomap.h"
#include "reset.h"
#include "flowctrl.h"
#include "sleep.h"
#include "tegra_cpu_car.h"

#define TEGRA_POWER_CPU_PWRREQ_OE (1 << 16) /* CPU pwr req enable */

#define PMC_CTRL 0x0
#define PMC_CPUPWRGOOD_TIMER 0xc8
#define PMC_CPUPWROFF_TIMER 0xcc

#ifdef CONFIG_PM_SLEEP
static unsigned int g_diag_reg;
static DEFINE_SPINLOCK(tegra_lp2_lock);
static void __iomem *pmc = IO_ADDRESS(TEGRA_PMC_BASE);
static struct clk *tegra_pclk;
void (*tegra_tear_down_cpu)(void);

void save_cpu_arch_register(void)
{
Expand All @@ -42,6 +65,89 @@ void restore_cpu_arch_register(void)
return;
}

static void set_power_timers(unsigned long us_on, unsigned long us_off)
{
unsigned long long ticks;
unsigned long long pclk;
unsigned long rate;
static unsigned long tegra_last_pclk;

if (tegra_pclk == NULL) {
tegra_pclk = clk_get_sys(NULL, "pclk");
WARN_ON(IS_ERR(tegra_pclk));
}

rate = clk_get_rate(tegra_pclk);

if (WARN_ON_ONCE(rate <= 0))
pclk = 100000000;
else
pclk = rate;

if ((rate != tegra_last_pclk)) {
ticks = (us_on * pclk) + 999999ull;
do_div(ticks, 1000000);
writel((unsigned long)ticks, pmc + PMC_CPUPWRGOOD_TIMER);

ticks = (us_off * pclk) + 999999ull;
do_div(ticks, 1000000);
writel((unsigned long)ticks, pmc + PMC_CPUPWROFF_TIMER);
wmb();
}
tegra_last_pclk = pclk;
}

/*
* restore_cpu_complex
*
* restores cpu clock setting, clears flow controller
*
* Always called on CPU 0.
*/
static void restore_cpu_complex(void)
{
int cpu = smp_processor_id();

BUG_ON(cpu != 0);

#ifdef CONFIG_SMP
cpu = cpu_logical_map(cpu);
#endif

/* Restore the CPU clock settings */
tegra_cpu_clock_resume();

flowctrl_cpu_suspend_exit(cpu);

restore_cpu_arch_register();
}

/*
* suspend_cpu_complex
*
* saves pll state for use by restart_plls, prepares flow controller for
* transition to suspend state
*
* Must always be called on cpu 0.
*/
static void suspend_cpu_complex(void)
{
int cpu = smp_processor_id();

BUG_ON(cpu != 0);

#ifdef CONFIG_SMP
cpu = cpu_logical_map(cpu);
#endif

/* Save the CPU clock settings */
tegra_cpu_clock_suspend();

flowctrl_cpu_suspend_enter(cpu);

save_cpu_arch_register();
}

void __cpuinit tegra_clear_cpu_in_lp2(int phy_cpu_id)
{
u32 *cpu_in_lp2 = tegra_cpu_lp2_mask;
Expand Down Expand Up @@ -71,4 +177,42 @@ bool __cpuinit tegra_set_cpu_in_lp2(int phy_cpu_id)
spin_unlock(&tegra_lp2_lock);
return last_cpu;
}

static int tegra_sleep_cpu(unsigned long v2p)
{
/* Switch to the identity mapping. */
cpu_switch_mm(idmap_pgd, &init_mm);

/* Flush the TLB. */
local_flush_tlb_all();

tegra_sleep_cpu_finish(v2p);

/* should never here */
BUG();

return 0;
}

void tegra_idle_lp2_last(u32 cpu_on_time, u32 cpu_off_time)
{
u32 mode;

/* Only the last cpu down does the final suspend steps */
mode = readl(pmc + PMC_CTRL);
mode |= TEGRA_POWER_CPU_PWRREQ_OE;
writel(mode, pmc + PMC_CTRL);

set_power_timers(cpu_on_time, cpu_off_time);

cpu_cluster_pm_enter();
suspend_cpu_complex();
outer_disable();

cpu_suspend(PHYS_OFFSET - PAGE_OFFSET, &tegra_sleep_cpu);

outer_resume();
restore_cpu_complex();
cpu_cluster_pm_exit();
}
#endif
3 changes: 3 additions & 0 deletions trunk/arch/arm/mach-tegra/pm.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,7 @@ void restore_cpu_arch_register(void);
void tegra_clear_cpu_in_lp2(int phy_cpu_id);
bool tegra_set_cpu_in_lp2(int phy_cpu_id);

void tegra_idle_lp2_last(u32 cpu_on_time, u32 cpu_off_time);
extern void (*tegra_tear_down_cpu)(void);

#endif /* _MACH_TEGRA_PM_H_ */
44 changes: 44 additions & 0 deletions trunk/arch/arm/mach-tegra/sleep-tegra30.S
Original file line number Diff line number Diff line change
Expand Up @@ -124,4 +124,48 @@ ENTRY(tegra30_sleep_cpu_secondary_finish)
mov r0, #1 @ never return here
mov pc, r7
ENDPROC(tegra30_sleep_cpu_secondary_finish)

/*
* tegra30_tear_down_cpu
*
* Switches the CPU to enter sleep.
*/
ENTRY(tegra30_tear_down_cpu)
mov32 r6, TEGRA_FLOW_CTRL_BASE

b tegra30_enter_sleep
ENDPROC(tegra30_tear_down_cpu)

/*
* tegra30_enter_sleep
*
* uses flow controller to enter sleep state
* executes from IRAM with SDRAM in selfrefresh when target state is LP0 or LP1
* executes from SDRAM with target state is LP2
* r6 = TEGRA_FLOW_CTRL_BASE
*/
tegra30_enter_sleep:
cpu_id r1

cpu_to_csr_reg r2, r1
ldr r0, [r6, r2]
orr r0, r0, #FLOW_CTRL_CSR_INTR_FLAG | FLOW_CTRL_CSR_EVENT_FLAG
orr r0, r0, #FLOW_CTRL_CSR_ENABLE
str r0, [r6, r2]

mov r0, #FLOW_CTRL_WAIT_FOR_INTERRUPT
orr r0, r0, #FLOW_CTRL_HALT_CPU_IRQ | FLOW_CTRL_HALT_CPU_FIQ
cpu_to_halt_reg r2, r1
str r0, [r6, r2]
dsb
ldr r0, [r6, r2] /* memory barrier */

halted:
isb
dsb
wfi /* CPU should be power gated here */

/* !!!FIXME!!! Implement halt failure handler */
b halted

#endif
42 changes: 42 additions & 0 deletions trunk/arch/arm/mach-tegra/sleep.S
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
#include <linux/linkage.h>

#include <asm/assembler.h>
#include <asm/cache.h>
#include <asm/cp15.h>

#include "iomap.h"
Expand Down Expand Up @@ -59,4 +60,45 @@ ENTRY(tegra_disable_clean_inv_dcache)
ldmfd sp!, {r0, r4-r5, r7, r9-r11, pc}
ENDPROC(tegra_disable_clean_inv_dcache)

/*
* tegra_sleep_cpu_finish(unsigned long v2p)
*
* enters suspend in LP2 by turning off the mmu and jumping to
* tegra?_tear_down_cpu
*/
ENTRY(tegra_sleep_cpu_finish)
/* Flush and disable the L1 data cache */
bl tegra_disable_clean_inv_dcache

mov32 r6, tegra_tear_down_cpu
ldr r1, [r6]
add r1, r1, r0

mov32 r3, tegra_shut_off_mmu
add r3, r3, r0
mov r0, r1

mov pc, r3
ENDPROC(tegra_sleep_cpu_finish)

/*
* tegra_shut_off_mmu
*
* r0 = physical address to jump to with mmu off
*
* called with VA=PA mapping
* turns off MMU, icache, dcache and branch prediction
*/
.align L1_CACHE_SHIFT
.pushsection .idmap.text, "ax"
ENTRY(tegra_shut_off_mmu)
mrc p15, 0, r3, c1, c0, 0
movw r2, #CR_I | CR_Z | CR_C | CR_M
bic r3, r3, r2
dsb
mcr p15, 0, r3, c1, c0, 0
isb
mov pc, r0
ENDPROC(tegra_shut_off_mmu)
.popsection
#endif
2 changes: 2 additions & 0 deletions trunk/arch/arm/mach-tegra/sleep.h
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@
.endm
#else
void tegra_resume(void);
int tegra_sleep_cpu_finish(unsigned long);

#ifdef CONFIG_HOTPLUG_CPU
void tegra20_hotplug_init(void);
Expand All @@ -83,6 +84,7 @@ static inline void tegra30_hotplug_init(void) {}
#endif

int tegra30_sleep_cpu_secondary_finish(unsigned long);
void tegra30_tear_down_cpu(void);

#endif
#endif

0 comments on commit e96794c

Please sign in to comment.