Skip to content

Commit

Permalink
ARM: tegra30: cpuidle: add powered-down state for secondary CPUs
Browse files Browse the repository at this point in the history
This supports power-gated idle on secondary CPUs for Tegra30. The
secondary CPUs can go into powered-down state independently. When
CPU goes into this state, it saves it's contexts and puts itself
to flow controlled WFI state. After that, it will been power gated.

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".

Based 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 d3f2936 commit d457ef3
Show file tree
Hide file tree
Showing 8 changed files with 253 additions and 0 deletions.
1 change: 1 addition & 0 deletions arch/arm/mach-tegra/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ obj-y += pmc.o
obj-y += flowctrl.o
obj-y += powergate.o
obj-y += apbio.o
obj-y += pm.o
obj-$(CONFIG_CPU_IDLE) += cpuidle.o
obj-$(CONFIG_CPU_IDLE) += sleep.o
obj-$(CONFIG_ARCH_TEGRA_2x_SOC) += tegra20_clocks.o
Expand Down
86 changes: 86 additions & 0 deletions arch/arm/mach-tegra/cpuidle-tegra30.c
Original file line number Diff line number Diff line change
Expand Up @@ -22,21 +22,107 @@
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/cpuidle.h>
#include <linux/cpu_pm.h>
#include <linux/clockchips.h>

#include <asm/cpuidle.h>
#include <asm/proc-fns.h>
#include <asm/suspend.h>
#include <asm/smp_plat.h>

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

#ifdef CONFIG_PM_SLEEP
static int tegra30_idle_lp2(struct cpuidle_device *dev,
struct cpuidle_driver *drv,
int index);
#endif

static struct cpuidle_driver tegra_idle_driver = {
.name = "tegra_idle",
.owner = THIS_MODULE,
.en_core_tk_irqen = 1,
#ifdef CONFIG_PM_SLEEP
.state_count = 2,
#else
.state_count = 1,
#endif
.states = {
[0] = ARM_CPUIDLE_WFI_STATE_PWR(600),
#ifdef CONFIG_PM_SLEEP
[1] = {
.enter = tegra30_idle_lp2,
.exit_latency = 2000,
.target_residency = 2200,
.power_usage = 0,
.flags = CPUIDLE_FLAG_TIME_VALID,
.name = "powered-down",
.desc = "CPU power gated",
},
#endif
},
};

static DEFINE_PER_CPU(struct cpuidle_device, tegra_idle_device);

#ifdef CONFIG_PM_SLEEP
#ifdef CONFIG_SMP
static bool tegra30_cpu_core_power_down(struct cpuidle_device *dev,
struct cpuidle_driver *drv,
int index)
{
clockevents_notify(CLOCK_EVT_NOTIFY_BROADCAST_ENTER, &dev->cpu);

smp_wmb();

save_cpu_arch_register();

cpu_suspend(0, tegra30_sleep_cpu_secondary_finish);

restore_cpu_arch_register();

clockevents_notify(CLOCK_EVT_NOTIFY_BROADCAST_EXIT, &dev->cpu);

return true;
}
#else
static inline bool tegra30_cpu_core_power_down(struct cpuidle_device *dev,
struct cpuidle_driver *drv,
int index)
{
return true;
}
#endif

static int __cpuinit tegra30_idle_lp2(struct cpuidle_device *dev,
struct cpuidle_driver *drv,
int index)
{
u32 cpu = is_smp() ? cpu_logical_map(dev->cpu) : dev->cpu;
bool entered_lp2 = false;

local_fiq_disable();

tegra_set_cpu_in_lp2(cpu);
cpu_pm_enter();

if (cpu == 0)
cpu_do_idle();
else
entered_lp2 = tegra30_cpu_core_power_down(dev, drv, index);

cpu_pm_exit();
tegra_clear_cpu_in_lp2(cpu);

local_fiq_enable();

smp_rmb();

return (entered_lp2) ? index : 0;
}
#endif

int __init tegra30_cpuidle_init(void)
{
int ret;
Expand Down
74 changes: 74 additions & 0 deletions arch/arm/mach-tegra/pm.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/*
* CPU complex suspend & resume functions for Tegra SoCs
*
* Copyright (c) 2009-2012, NVIDIA Corporation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

#include <linux/kernel.h>
#include <linux/spinlock.h>
#include <linux/io.h>
#include <linux/cpumask.h>

#include "iomap.h"
#include "reset.h"

#ifdef CONFIG_PM_SLEEP
static unsigned int g_diag_reg;
static DEFINE_SPINLOCK(tegra_lp2_lock);

void save_cpu_arch_register(void)
{
/* read diagnostic register */
asm("mrc p15, 0, %0, c15, c0, 1" : "=r"(g_diag_reg) : : "cc");
return;
}

void restore_cpu_arch_register(void)
{
/* write diagnostic register */
asm("mcr p15, 0, %0, c15, c0, 1" : : "r"(g_diag_reg) : "cc");
return;
}

void __cpuinit tegra_clear_cpu_in_lp2(int phy_cpu_id)
{
u32 *cpu_in_lp2 = tegra_cpu_lp2_mask;

spin_lock(&tegra_lp2_lock);

BUG_ON(!(*cpu_in_lp2 & BIT(phy_cpu_id)));
*cpu_in_lp2 &= ~BIT(phy_cpu_id);

spin_unlock(&tegra_lp2_lock);
}

bool __cpuinit tegra_set_cpu_in_lp2(int phy_cpu_id)
{
bool last_cpu = false;
cpumask_t *cpu_lp2_mask = tegra_cpu_lp2_mask;
u32 *cpu_in_lp2 = tegra_cpu_lp2_mask;

spin_lock(&tegra_lp2_lock);

BUG_ON((*cpu_in_lp2 & BIT(phy_cpu_id)));
*cpu_in_lp2 |= BIT(phy_cpu_id);

if ((phy_cpu_id == 0) && cpumask_equal(cpu_lp2_mask, cpu_online_mask))
last_cpu = true;

spin_unlock(&tegra_lp2_lock);
return last_cpu;
}
#endif
30 changes: 30 additions & 0 deletions arch/arm/mach-tegra/pm.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
* Copyright (C) 2010 Google, Inc.
* Copyright (c) 2010-2012 NVIDIA Corporation. All rights reserved.
*
* Author:
* Colin Cross <ccross@google.com>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

#ifndef _MACH_TEGRA_PM_H_
#define _MACH_TEGRA_PM_H_

void save_cpu_arch_register(void);
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);

#endif /* _MACH_TEGRA_PM_H_ */
9 changes: 9 additions & 0 deletions arch/arm/mach-tegra/reset.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,22 @@

#ifndef __ASSEMBLY__

#include "irammap.h"

extern unsigned long __tegra_cpu_reset_handler_data[TEGRA_RESET_DATA_SIZE];

void __tegra_cpu_reset_handler_start(void);
void __tegra_cpu_reset_handler(void);
void __tegra_cpu_reset_handler_end(void);
void tegra_secondary_startup(void);

#ifdef CONFIG_PM_SLEEP
#define tegra_cpu_lp2_mask \
(IO_ADDRESS(TEGRA_IRAM_BASE + TEGRA_IRAM_RESET_HANDLER_OFFSET + \
((u32)&__tegra_cpu_reset_handler_data[TEGRA_RESET_MASK_LP2] - \
(u32)__tegra_cpu_reset_handler_start)))
#endif

#define tegra_cpu_reset_handler_offset \
((u32)__tegra_cpu_reset_handler - \
(u32)__tegra_cpu_reset_handler_start)
Expand Down
22 changes: 22 additions & 0 deletions arch/arm/mach-tegra/sleep-tegra30.S
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
#include <linux/linkage.h>

#include <asm/assembler.h>
#include <asm/asm-offsets.h>

#include "sleep.h"
#include "flowctrl.h"
Expand Down Expand Up @@ -80,6 +81,7 @@ delay_1:
ldr r3, [r1] @ read CSR
str r3, [r1] @ clear CSR
tst r0, #TEGRA30_POWER_HOTPLUG_SHUTDOWN
moveq r3, #FLOW_CTRL_WAIT_FOR_INTERRUPT @ For LP2
movne r3, #FLOW_CTRL_WAITEVENT @ For hotplug
str r3, [r2]
ldr r0, [r2]
Expand All @@ -103,3 +105,23 @@ wfe_war:

ENDPROC(tegra30_cpu_shutdown)
#endif

#ifdef CONFIG_PM_SLEEP
/*
* tegra30_sleep_cpu_secondary_finish(unsigned long v2p)
*
* Enters LP2 on secondary CPU by exiting coherency and powergating the CPU.
*/
ENTRY(tegra30_sleep_cpu_secondary_finish)
mov r7, lr

/* Flush and disable the L1 data cache */
bl tegra_disable_clean_inv_dcache

/* Powergate this CPU. */
mov r0, #0 @ power mode flags (!hotplug)
bl tegra30_cpu_shutdown
mov r0, #1 @ never return here
mov pc, r7
ENDPROC(tegra30_sleep_cpu_secondary_finish)
#endif
29 changes: 29 additions & 0 deletions arch/arm/mach-tegra/sleep.S
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,38 @@
#include <linux/linkage.h>

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

#include "iomap.h"

#include "flowctrl.h"
#include "sleep.h"

#ifdef CONFIG_PM_SLEEP
/*
* tegra_disable_clean_inv_dcache
*
* disable, clean & invalidate the D-cache
*
* Corrupted registers: r1-r3, r6, r8, r9-r11
*/
ENTRY(tegra_disable_clean_inv_dcache)
stmfd sp!, {r0, r4-r5, r7, r9-r11, lr}
dmb @ ensure ordering

/* Disable the D-cache */
mrc p15, 0, r2, c1, c0, 0
bic r2, r2, #CR_C
mcr p15, 0, r2, c1, c0, 0
isb

/* Flush the D-cache */
bl v7_flush_dcache_louis

/* Trun off coherency */
exit_smp r4, r5

ldmfd sp!, {r0, r4-r5, r7, r9-r11, pc}
ENDPROC(tegra_disable_clean_inv_dcache)

#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 @@ -82,5 +82,7 @@ static inline void tegra20_hotplug_init(void) {}
static inline void tegra30_hotplug_init(void) {}
#endif

int tegra30_sleep_cpu_secondary_finish(unsigned long);

#endif
#endif

0 comments on commit d457ef3

Please sign in to comment.