Skip to content

Commit

Permalink
ARM: tegra30: cpuidle: add powered-down state for CPU0
Browse files Browse the repository at this point in the history
This is a power gating idle mode. It support power gating vdd_cpu rail
after all cpu cores in "powered-down" status. For Tegra30, the CPU0 can
enter this state only when all secondary CPU is offline. We need to take
care and make sure whole secondary CPUs were offline and checking the
CPU power gate status. After that, the CPU0 can go into "powered-down"
state safely. Then shut off the CPU rail.

Be aware of that, you may see the legacy power state "LP2" in the code
which is exactly the same meaning of "CPU power down".

Base on the work by:
Scott Williams <scwilliams@nvidia.com>

Signed-off-by: Joseph Lo <josephl@nvidia.com>
Signed-off-by: Stephen Warren <swarren@nvidia.com>
  • Loading branch information
Joseph Lo authored and Stephen Warren committed Nov 15, 2012
1 parent 01459c6 commit d552920
Show file tree
Hide file tree
Showing 6 changed files with 275 additions and 4 deletions.
44 changes: 40 additions & 4 deletions 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 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 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 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 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 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 d552920

Please sign in to comment.