diff options
Diffstat (limited to 'arch/arm/mach-tegra')
-rw-r--r-- | arch/arm/mach-tegra/board-nvodm.c | 81 | ||||
-rw-r--r-- | arch/arm/mach-tegra/board.h | 4 | ||||
-rw-r--r-- | arch/arm/mach-tegra/common.c | 1 | ||||
-rw-r--r-- | arch/arm/mach-tegra/power.h | 13 | ||||
-rw-r--r-- | arch/arm/mach-tegra/suspend.c | 217 |
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 +} |