summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGary King <gking@nvidia.com>2010-05-26 21:40:35 -0700
committerGary King <gking@nvidia.com>2010-05-26 21:49:08 -0700
commit8a4c20e35a435e8744618a19fd51d3d23ac5d16b (patch)
tree254ceae0e2f238db74df715b1831d9ac9a6c6b88
parent6e046b849857d08142c00a1febc6c34e778007cc (diff)
[ARM/tegra] suspend: add suspend to LP2
implement basic support for system suspend operations using LP2 (CPU power-gating) platform-specific data (power good times, PMU capabilities, etc.) must be specified when registering the suspend operations, and a helper function for mapping from wakeup pad and PMU property data from the ODM kit to the platform_data structure is provided. AVP & RM suspend is performed in the prepare_late callback, ensuring that these operations are executed after all drivers have suspended, to eliminate ordering conflicts on RM dependencies since all device interrupts (except timers) are disabled in the suspend path, the wakeup interrupts need to be manually unmasked before entering into a suspend state or the processor will never wake up; these forced-unmask interrupts are re-masked immediately in the resume path to prevent the kernel from live-locking prior to driver resume. Change-Id: Ibe4d594d450b253744d803a0a15d66ae275029e8
-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
+}