summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--arch/arm/mach-tegra/board-nvodm.c81
-rw-r--r--arch/arm/mach-tegra/board.h4
-rw-r--r--arch/arm/mach-tegra/common.c1
-rw-r--r--arch/arm/mach-tegra/power.h13
-rw-r--r--arch/arm/mach-tegra/suspend.c217
5 files changed, 306 insertions, 10 deletions
diff --git a/arch/arm/mach-tegra/board-nvodm.c b/arch/arm/mach-tegra/board-nvodm.c
index 3a9b037050ef..f6ece6423a90 100644
--- a/arch/arm/mach-tegra/board-nvodm.c
+++ b/arch/arm/mach-tegra/board-nvodm.c
@@ -28,6 +28,7 @@
#include <linux/dma-mapping.h>
#include <linux/regulator/machine.h>
#include <linux/lbee9qmb-rfkill.h>
+#include <linux/gpio.h>
#include <mach/iomap.h>
#include <mach/io.h>
@@ -52,6 +53,9 @@
#include "nvodm_kbc.h"
#include "nvodm_query_kbc.h"
#include "nvodm_kbc_keymapping.h"
+#include "gpio-names.h"
+#include "power.h"
+#include "board.h"
NvRmGpioHandle s_hGpioGlobal;
@@ -897,6 +901,82 @@ static void tegra_system_power_off(void)
}
}
+static struct tegra_suspend_platform_data tegra_suspend_platform = {
+ .cpu_timer = 2000,
+};
+
+static void __init tegra_setup_suspend(void)
+{
+#ifdef CONFIG_ARCH_TEGRA_2x_SOC
+ const int wakepad_irq[] = {
+ gpio_to_irq(TEGRA_GPIO_PO5), gpio_to_irq(TEGRA_GPIO_PV3),
+ gpio_to_irq(TEGRA_GPIO_PL1), gpio_to_irq(TEGRA_GPIO_PB6),
+ gpio_to_irq(TEGRA_GPIO_PN7), gpio_to_irq(TEGRA_GPIO_PA0),
+ gpio_to_irq(TEGRA_GPIO_PU5), gpio_to_irq(TEGRA_GPIO_PU6),
+ gpio_to_irq(TEGRA_GPIO_PC7), gpio_to_irq(TEGRA_GPIO_PS2),
+ gpio_to_irq(TEGRA_GPIO_PAA1), gpio_to_irq(TEGRA_GPIO_PW3),
+ gpio_to_irq(TEGRA_GPIO_PW2), gpio_to_irq(TEGRA_GPIO_PY6),
+ gpio_to_irq(TEGRA_GPIO_PV6), gpio_to_irq(TEGRA_GPIO_PJ7),
+ INT_RTC, INT_KBC, INT_EXTERNAL_PMU,
+ /* FIXME: USB wake pad interrupt mapping may be wrong */
+ INT_USB, INT_USB3, INT_USB, INT_USB3,
+ gpio_to_irq(TEGRA_GPIO_PI5), gpio_to_irq(TEGRA_GPIO_PV2),
+ gpio_to_irq(TEGRA_GPIO_PS4), gpio_to_irq(TEGRA_GPIO_PS5),
+ gpio_to_irq(TEGRA_GPIO_PS0), gpio_to_irq(TEGRA_GPIO_PQ6),
+ gpio_to_irq(TEGRA_GPIO_PQ7), gpio_to_irq(TEGRA_GPIO_PN2),
+ };
+#endif
+ const NvOdmWakeupPadInfo *w;
+ const NvOdmSocPowerStateInfo *lp;
+ struct tegra_suspend_platform_data *plat = &tegra_suspend_platform;
+ NvOdmPmuProperty pmu;
+ NvBool has_pmu;
+ NvU32 nr_wake;
+
+ lp = NvOdmQueryLowestSocPowerState();
+ w = NvOdmQueryGetWakeupPadTable(&nr_wake);
+ has_pmu = NvOdmQueryGetPmuProperty(&pmu);
+
+ if (!has_pmu) {
+ pr_info("%s: no PMU property, ignoring all suspend state\n",
+ __func__);
+ goto do_register;
+ }
+
+ if (lp->LowestPowerState==NvOdmSocPowerState_Suspend) {
+ plat->dram_suspend = true;
+ plat->core_off = false;
+ } else if (lp->LowestPowerState==NvOdmSocPowerState_DeepSleep) {
+ plat->dram_suspend = true;
+ plat->core_off = true;
+ }
+
+ if (has_pmu) {
+ plat->cpu_timer = pmu.CpuPowerGoodUs;
+ plat->core_timer = pmu.PowerGoodCount;
+ plat->separate_req = !pmu.CombinedPowerReq;
+ plat->corereq_high =
+ (pmu.CorePowerReqPolarity ==
+ NvOdmCorePowerReqPolarity_High);
+ plat->sysclkreq_high =
+ (pmu.SysClockReqPolarity ==
+ NvOdmCorePowerReqPolarity_High);
+ }
+
+ if (!w || !nr_wake)
+ goto do_register;
+
+ while (nr_wake--) {
+ unsigned int pad = w->WakeupPadNumber;
+ if (pad < ARRAY_SIZE(wakepad_irq) && w->enable)
+ enable_irq_wake(wakepad_irq[w->WakeupPadNumber]);
+ w++;
+ }
+
+do_register:
+ tegra_init_suspend(plat);
+}
+
void __init tegra_setup_nvodm(void)
{
NvRmGpioOpen(s_hRmGlobal, &s_hGpioGlobal);
@@ -908,4 +988,5 @@ void __init tegra_setup_nvodm(void)
tegra_setup_kbc();
platform_add_devices(nvodm_devices, ARRAY_SIZE(nvodm_devices));
pm_power_off = tegra_system_power_off;
+ tegra_setup_suspend();
}
diff --git a/arch/arm/mach-tegra/board.h b/arch/arm/mach-tegra/board.h
index 85195a8cc87d..702a52ff1936 100644
--- a/arch/arm/mach-tegra/board.h
+++ b/arch/arm/mach-tegra/board.h
@@ -23,11 +23,13 @@
#include <linux/types.h>
+struct tegra_suspend_platform_data;
+
void __init tegra_common_init(void);
void __init tegra_map_common_io(void);
void __init tegra_init_irq(void);
void __init tegra_init_clock(void);
-void __init tegra_init_suspend(void);
+void __init tegra_init_suspend(struct tegra_suspend_platform_data *plat);
#ifdef CONFIG_CPU_FREQ
int tegra_start_dvfsd(void);
diff --git a/arch/arm/mach-tegra/common.c b/arch/arm/mach-tegra/common.c
index 4287b26ba569..96d7091dea43 100644
--- a/arch/arm/mach-tegra/common.c
+++ b/arch/arm/mach-tegra/common.c
@@ -65,7 +65,6 @@ void __init tegra_common_init(void)
nvmap_add_carveout_heap(TEGRA_IRAM_BASE, TEGRA_IRAM_SIZE,
"iram", NVMEM_HEAP_CARVEOUT_IRAM);
tegra_init_clock();
- tegra_init_suspend();
tegra_init_cache();
tegra_dma_init();
arm_pm_restart = tegra_machine_restart;
diff --git a/arch/arm/mach-tegra/power.h b/arch/arm/mach-tegra/power.h
index 5763c4743e9a..ee0916863a06 100644
--- a/arch/arm/mach-tegra/power.h
+++ b/arch/arm/mach-tegra/power.h
@@ -33,12 +33,25 @@
#define TEGRA_POWER_EFFECT_LP0 0x40 /* enter LP0 when CPU pwr gated */
#define TEGRA_POWER_CPU_PWRREQ_POLARITY 0x80 /* CPU power request polarity */
#define TEGRA_POWER_CPU_PWRREQ_OE 0x100 /* CPU power request enable */
+#define TEGRA_POWER_PMC_SHIFT 8
+#define TEGRA_POWER_PMC_MASK 0x1ff
#ifndef __ASSEMBLY__
void tegra_lp2_set_trigger(unsigned long cycles);
void __cortex_a9_save(unsigned int mode);
void tegra_lp2_startup(void);
+
+struct tegra_suspend_platform_data {
+ unsigned long cpu_timer; /* CPU power good time in us, LP2 */
+ unsigned long core_timer; /* core power good time in ticks, LP0/LP1 */
+ bool dram_suspend; /* platform supports DRAM self-refresh */
+ bool core_off; /* platform supports core voltage off */
+ bool corereq_high; /* Core power request active-high */
+ bool sysclkreq_high; /* System clock request is active-high */
+ bool separate_req; /* Core & CPU power request are separate */
+};
+
#endif
#endif
diff --git a/arch/arm/mach-tegra/suspend.c b/arch/arm/mach-tegra/suspend.c
index 4e1bb157aef7..29edcb9a0696 100644
--- a/arch/arm/mach-tegra/suspend.c
+++ b/arch/arm/mach-tegra/suspend.c
@@ -28,6 +28,9 @@
#include <linux/irq.h>
#include <linux/interrupt.h>
#include <linux/clk.h>
+#include <linux/err.h>
+#include <linux/delay.h>
+#include <linux/suspend.h>
#include <asm/cacheflush.h>
#include <asm/hardware/gic.h>
@@ -37,6 +40,10 @@
#include <mach/iomap.h>
#include <mach/irqs.h>
+#include <mach/nvrm_linux.h>
+
+#include <nvrm_memmgr.h>
+#include "nvrm/core/common/nvrm_message.h"
#include "power.h"
@@ -62,6 +69,7 @@ static void __iomem *evp_reset = IO_ADDRESS(TEGRA_EXCEPTION_VECTORS_BASE)+0x100;
static void __iomem *tmrus = IO_ADDRESS(TEGRA_TMRUS_BASE);
#define PMC_CTRL 0x0
+#define PMC_COREPWRGOOD_TIMER 0x3c
#define PMC_SCRATCH1 0x54
#define PMC_CPUPWRGOOD_TIMER 0xc8
#define PMC_SCRATCH38 0x134
@@ -81,12 +89,7 @@ static void __iomem *tmrus = IO_ADDRESS(TEGRA_TMRUS_BASE);
#define FLOW_CTRL_CPU1_CSR 0x18
static struct clk *tegra_pclk = NULL;
-
-void __init tegra_init_suspend(void)
-{
- tegra_pclk = clk_get_sys(NULL, "pclk");
- BUG_ON(!tegra_pclk);
-}
+static const struct tegra_suspend_platform_data *pdata = NULL;
static void set_powergood_time(unsigned int us)
{
@@ -195,9 +198,10 @@ unsigned int tegra_suspend_lp2(unsigned int us)
/* FIXME: power good time (in us) should come from the board file,
* not hard-coded here. */
- set_powergood_time(2000);
+ set_powergood_time(pdata->cpu_timer);
- tegra_lp2_set_trigger(us);
+ if (us)
+ tegra_lp2_set_trigger(us);
suspend_cpu_complex();
flush_cache_all();
/* structure is written by reset code, so the L2 lines
@@ -215,3 +219,200 @@ unsigned int tegra_suspend_lp2(unsigned int us)
exit = readl(pmc + PMC_SCRATCH39);
return exit - entry;
}
+
+#ifdef CONFIG_PM
+static int tegra_suspend_prepare_late(void)
+{
+#ifdef CONFIG_TEGRA_NVRM
+ static NvRmTransportHandle port = NULL;
+ static NvRmMemHandle iram_area = NULL;
+ static unsigned long iram_area_pa = 0;
+ static void __iomem *barrier = NULL;
+
+ NvRmMessage_InitiateLP0 msg;
+ unsigned long timeout;
+ NvError e;
+
+ if (!s_hRmGlobal)
+ return 0;
+
+ if (!port) {
+ e = NvRmTransportOpen(s_hRmGlobal, "RPC_AVP_PORT", NULL, &port);
+ if (e != NvSuccess) {
+ pr_err("%s: aborting suspend due to TransportOpen "
+ "returning 0x%08x\n", __func__, e);
+ return -ENOSYS;
+ }
+ }
+
+ if (!iram_area) {
+ NvRmHeap h = NvRmHeap_ExternalCarveOut;
+
+ e = NvRmMemHandleCreate(s_hRmGlobal, &iram_area,
+ TEGRA_IRAM_SIZE + PAGE_SIZE);
+ if (e != NvSuccess) {
+ pr_err("%s: MemHandleCreate returned 0x%08x\n",
+ __func__, e);
+ return -ENOMEM;
+ }
+ e = NvRmMemAlloc(iram_area, &h, 1, PAGE_SIZE,
+ NvOsMemAttribute_Uncached);
+ if (e != NvSuccess) {
+ pr_err("%s: NvRmMemAlloc returned 0x%08x\n",
+ __func__, e);
+ NvRmMemHandleFree(iram_area);
+ iram_area = NULL;
+ return -ENOMEM;
+ }
+
+ iram_area_pa = NvRmMemPin(iram_area);
+
+ }
+
+ BUG_ON(iram_area_pa == ~0ul);
+
+ if (!barrier) {
+ barrier = ioremap(iram_area_pa + TEGRA_IRAM_SIZE, PAGE_SIZE);
+ if (IS_ERR_OR_NULL(barrier)) {
+ pr_err("%s: failed to map barrier\n", __func__);
+ barrier = NULL;
+ return -ENOMEM;
+ }
+ }
+
+ /* the AVP will write a non-zero value to PMC_SCRATCH38 once it has
+ * suspended itself */
+ msg.msg = NvRmMsg_InitiateLP0;
+ msg.sourceAddr = TEGRA_IRAM_BASE;
+ msg.bufferAddr = iram_area_pa;
+ msg.bufferSize = TEGRA_IRAM_SIZE;
+
+ writel(0, barrier);
+ wmb();
+
+ e = NvRmTransportSendMsgInLP0(port, &msg, sizeof(msg));
+ if (e != NvSuccess) {
+ pr_err("%s: aborting suspend due to SendMsgInLP0 returning "
+ "0x%08x\n", __func__, e);
+ return -EIO;
+ }
+ timeout = jiffies + msecs_to_jiffies(1000);
+
+ while (time_before(jiffies, timeout)) {
+ if (readl(barrier))
+ break;
+ udelay(10);
+ rmb();
+ }
+
+ /* FIXME: reset the AVP, don't abort suspend */
+ if (!readl(barrier)) {
+ pr_err("%s: aborting suspend due to AVP timeout\n", __func__);
+ return -EIO;
+ }
+ e = NvRmKernelPowerSuspend(s_hRmGlobal);
+ if (e != NvSuccess) {
+ pr_err("%s: aborting suspend due to RM failure 0x%08x\n",
+ __func__, e);
+ return -EIO;
+ }
+#endif
+ return 0;
+}
+
+static void tegra_suspend_wake(void)
+{
+#ifdef CONFIG_TEGRA_NVRM
+ NvError e;
+
+ e = NvRmKernelPowerResume(s_hRmGlobal);
+ if (e != NvSuccess)
+ panic("%s: RM resume failed 0x%08x!\n", __func__, e);
+#endif
+}
+
+extern void tegra_pinmux_suspend(void);
+extern void tegra_irq_suspend(void);
+extern void tegra_gpio_suspend(void);
+
+extern void tegra_pinmux_resume(void);
+extern void tegra_irq_resume(void);
+extern void tegra_gpio_resume(void);
+
+static int tegra_suspend_enter(suspend_state_t state)
+{
+ struct irq_desc *desc;
+ unsigned long flags;
+ int irq;
+
+ local_irq_save(flags);
+ tegra_irq_suspend();
+ tegra_pinmux_suspend();
+ tegra_gpio_suspend();
+
+ for_each_irq_desc(irq, desc) {
+ if ((desc->status & IRQ_WAKEUP) &&
+ (desc->status & IRQ_SUSPENDED)) {
+ get_irq_chip(irq)->unmask(irq);
+ }
+ }
+
+ tegra_suspend_lp2(0);
+
+ for_each_irq_desc(irq, desc) {
+ if ((desc->status & IRQ_WAKEUP) &&
+ (desc->status & IRQ_SUSPENDED)) {
+ get_irq_chip(irq)->mask(irq);
+ }
+ }
+
+ tegra_gpio_resume();
+ tegra_pinmux_resume();
+ tegra_irq_resume();
+
+ local_irq_restore(flags);
+
+ return 0;
+}
+
+static struct platform_suspend_ops tegra_suspend_ops = {
+ .valid = suspend_valid_only_mem,
+ .prepare_late = tegra_suspend_prepare_late,
+ .wake = tegra_suspend_wake,
+ .enter = tegra_suspend_enter,
+};
+#endif
+
+void __init tegra_init_suspend(struct tegra_suspend_platform_data *plat)
+{
+ tegra_pclk = clk_get_sys(NULL, "pclk");
+ BUG_ON(!tegra_pclk);
+ pdata = plat;
+
+ if (pdata->core_off) {
+ u32 reg = 0, mode;
+
+ writel(pdata->core_timer, pmc + PMC_COREPWRGOOD_TIMER);
+ reg = readl(pmc + PMC_CTRL);
+ mode = (reg >> TEGRA_POWER_PMC_SHIFT) & TEGRA_POWER_PMC_MASK;
+
+ mode &= ~TEGRA_POWER_SYSCLK_POLARITY;
+ mode &= ~TEGRA_POWER_PWRREQ_POLARITY;
+
+ if (!pdata->sysclkreq_high)
+ mode |= TEGRA_POWER_SYSCLK_POLARITY;
+ if (!pdata->corereq_high)
+ mode |= TEGRA_POWER_PWRREQ_POLARITY;
+
+ /* configure output inverters while the request is tristated */
+ reg |= (mode << TEGRA_POWER_PMC_SHIFT);
+ writel(reg, pmc + PMC_CTRL);
+ wmb();
+ udelay(2000); /* 32KHz domain delay */
+ reg |= (TEGRA_POWER_SYSCLK_OE << TEGRA_POWER_PMC_SHIFT);
+ writel(reg, pmc + PMC_CTRL);
+ }
+#ifdef CONFIG_PM
+ suspend_set_ops(&tegra_suspend_ops);
+#endif
+}