Skip to content

Commit

Permalink
ARM: tegra: pm: add platform suspend support
Browse files Browse the repository at this point in the history
Adding suspend to RAM support for Tegra platform. There are three suspend
mode for Tegra. The difference were below.

* LP2: CPU voltage off
* LP1: CPU voltage off, DRAM in self-refresh
* LP0: CPU + Core voltage off, DRAM in self-refresh

After this patch, the LP2 suspend mode will be supported.

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 Apr 3, 2013
1 parent 4b51ccb commit c8c2e60
Show file tree
Hide file tree
Showing 5 changed files with 146 additions and 17 deletions.
1 change: 1 addition & 0 deletions arch/arm/mach-tegra/common.c
Original file line number Diff line number Diff line change
Expand Up @@ -109,5 +109,6 @@ void __init tegra_init_early(void)

void __init tegra_init_late(void)
{
tegra_init_suspend();
tegra_powergate_debugfs_init();
}
93 changes: 80 additions & 13 deletions arch/arm/mach-tegra/pm.c
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
#include <linux/cpumask.h>
#include <linux/delay.h>
#include <linux/cpu_pm.h>
#include <linux/suspend.h>
#include <linux/err.h>
#include <linux/clk/tegra.h>

Expand All @@ -38,14 +39,10 @@
#include "fuse.h"
#include "pmc.h"
#include "sleep.h"

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

#define PMC_CTRL 0x0
#include "pmc.h"

#ifdef CONFIG_PM_SLEEP
static DEFINE_SPINLOCK(tegra_lp2_lock);
static void __iomem *pmc = IO_ADDRESS(TEGRA_PMC_BASE);
void (*tegra_tear_down_cpu)(void);

/*
Expand Down Expand Up @@ -145,14 +142,7 @@ static int tegra_sleep_cpu(unsigned long v2p)

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);
tegra_pmc_pm_set(TEGRA_SUSPEND_LP2);

cpu_cluster_pm_enter();
suspend_cpu_complex();
Expand All @@ -162,4 +152,81 @@ void tegra_idle_lp2_last(u32 cpu_on_time, u32 cpu_off_time)
restore_cpu_complex();
cpu_cluster_pm_exit();
}

enum tegra_suspend_mode tegra_pm_validate_suspend_mode(
enum tegra_suspend_mode mode)
{
/* Tegra114 didn't support any suspending mode yet. */
if (tegra_chip_id == TEGRA114)
return TEGRA_SUSPEND_NONE;

/*
* The Tegra devices only support suspending to LP2 currently.
*/
if (mode > TEGRA_SUSPEND_LP2)
return TEGRA_SUSPEND_LP2;

return mode;
}

static const char *lp_state[TEGRA_MAX_SUSPEND_MODE] = {
[TEGRA_SUSPEND_NONE] = "none",
[TEGRA_SUSPEND_LP2] = "LP2",
[TEGRA_SUSPEND_LP1] = "LP1",
[TEGRA_SUSPEND_LP0] = "LP0",
};

static int __cpuinit tegra_suspend_enter(suspend_state_t state)
{
enum tegra_suspend_mode mode = tegra_pmc_get_suspend_mode();

if (WARN_ON(mode < TEGRA_SUSPEND_NONE ||
mode >= TEGRA_MAX_SUSPEND_MODE))
return -EINVAL;

pr_info("Entering suspend state %s\n", lp_state[mode]);

tegra_pmc_pm_set(mode);

local_fiq_disable();

suspend_cpu_complex();
switch (mode) {
case TEGRA_SUSPEND_LP2:
tegra_set_cpu_in_lp2(0);
break;
default:
break;
}

cpu_suspend(PHYS_OFFSET - PAGE_OFFSET, &tegra_sleep_cpu);

switch (mode) {
case TEGRA_SUSPEND_LP2:
tegra_clear_cpu_in_lp2(0);
break;
default:
break;
}
restore_cpu_complex();

local_fiq_enable();

return 0;
}

static const struct platform_suspend_ops tegra_suspend_ops = {
.valid = suspend_valid_only_mem,
.enter = tegra_suspend_enter,
};

void __init tegra_init_suspend(void)
{
if (tegra_pmc_get_suspend_mode() == TEGRA_SUSPEND_NONE)
return;

tegra_pmc_suspend_init();

suspend_set_ops(&tegra_suspend_ops);
}
#endif
15 changes: 15 additions & 0 deletions arch/arm/mach-tegra/pm.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
#ifndef _MACH_TEGRA_PM_H_
#define _MACH_TEGRA_PM_H_

#include "pmc.h"

extern unsigned long l2x0_saved_regs_addr;

void save_cpu_arch_register(void);
Expand All @@ -32,4 +34,17 @@ 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);

#ifdef CONFIG_PM_SLEEP
enum tegra_suspend_mode tegra_pm_validate_suspend_mode(
enum tegra_suspend_mode mode);
void tegra_init_suspend(void);
#else
enum tegra_suspend_mode tegra_pm_validate_suspend_mode(
enum tegra_suspend_mode mode)
{
return TEGRA_SUSPEND_NONE;
}
static inline void tegra_init_suspend(void) {}
#endif

#endif /* _MACH_TEGRA_PM_H_ */
50 changes: 47 additions & 3 deletions arch/arm/mach-tegra/pmc.c
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,14 @@
#include <linux/of.h>
#include <linux/of_address.h>

#include "fuse.h"
#include "pm.h"
#include "pmc.h"
#include "sleep.h"

#define TEGRA_POWER_EFFECT_LP0 (1 << 14) /* LP0 when CPU pwr gated */
#define TEGRA_POWER_CPU_PWRREQ_POLARITY (1 << 15) /* CPU pwr req polarity */
#define TEGRA_POWER_CPU_PWRREQ_OE (1 << 16) /* CPU pwr req enable */

#define PMC_CTRL 0x0
#define PMC_CTRL_INTR_LOW (1 << 17)
Expand Down Expand Up @@ -157,14 +164,12 @@ int tegra_pmc_cpu_remove_clamping(int cpuid)
}

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

rate = clk_get_rate(tegra_pclk);
if (WARN_ON_ONCE(rate <= 0))
pclk = 100000000;
else
Expand All @@ -182,6 +187,44 @@ void set_power_timers(unsigned long us_on, unsigned long us_off)
}
tegra_last_pclk = pclk;
}

enum tegra_suspend_mode tegra_pmc_get_suspend_mode(void)
{
return pmc_pm_data.suspend_mode;
}

void tegra_pmc_pm_set(enum tegra_suspend_mode mode)
{
u32 reg;
unsigned long rate = 0;

reg = tegra_pmc_readl(PMC_CTRL);
reg |= TEGRA_POWER_CPU_PWRREQ_OE;
reg &= ~TEGRA_POWER_EFFECT_LP0;

switch (mode) {
case TEGRA_SUSPEND_LP2:
rate = clk_get_rate(tegra_pclk);
break;
default:
break;
}

set_power_timers(pmc_pm_data.cpu_good_time, pmc_pm_data.cpu_off_time,
rate);

tegra_pmc_writel(reg, PMC_CTRL);
}

void tegra_pmc_suspend_init(void)
{
u32 reg;

/* Always enable CPU power request */
reg = tegra_pmc_readl(PMC_CTRL);
reg |= TEGRA_POWER_CPU_PWRREQ_OE;
tegra_pmc_writel(reg, PMC_CTRL);
}
#endif

static const struct of_device_id matches[] __initconst = {
Expand Down Expand Up @@ -228,6 +271,7 @@ static void tegra_pmc_parse_dt(void)
break;
}
}
suspend_mode = tegra_pm_validate_suspend_mode(suspend_mode);

if (of_property_read_u32(np, "nvidia,cpu-pwr-good-time", &prop))
suspend_mode = TEGRA_SUSPEND_NONE;
Expand Down
4 changes: 3 additions & 1 deletion arch/arm/mach-tegra/pmc.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,9 @@ enum tegra_suspend_mode {
};

#ifdef CONFIG_PM_SLEEP
void set_power_timers(unsigned long us_on, unsigned long us_off);
enum tegra_suspend_mode tegra_pmc_get_suspend_mode(void);
void tegra_pmc_pm_set(enum tegra_suspend_mode mode);
void tegra_pmc_suspend_init(void);
#endif

bool tegra_pmc_cpu_is_powered(int cpuid);
Expand Down

0 comments on commit c8c2e60

Please sign in to comment.