From patchwork Wed Jan 8 08:51:04 2014 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Anson Huang X-Patchwork-Id: 3452801 Return-Path: X-Original-To: patchwork-linux-arm@patchwork.kernel.org Delivered-To: patchwork-parsemail@patchwork1.web.kernel.org Received: from mail.kernel.org (mail.kernel.org [198.145.19.201]) by patchwork1.web.kernel.org (Postfix) with ESMTP id 896729F1C4 for ; Wed, 8 Jan 2014 08:52:00 +0000 (UTC) Received: from mail.kernel.org (localhost [127.0.0.1]) by mail.kernel.org (Postfix) with ESMTP id E42432010B for ; Wed, 8 Jan 2014 08:51:54 +0000 (UTC) Received: from casper.infradead.org (casper.infradead.org [85.118.1.10]) (using TLSv1.2 with cipher DHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by mail.kernel.org (Postfix) with ESMTPS id 803C220103 for ; Wed, 8 Jan 2014 08:51:52 +0000 (UTC) Received: from merlin.infradead.org ([2001:4978:20e::2]) by casper.infradead.org with esmtps (Exim 4.80.1 #2 (Red Hat Linux)) id 1W0orU-0001C1-7m; Wed, 08 Jan 2014 08:51:40 +0000 Received: from localhost ([::1] helo=merlin.infradead.org) by merlin.infradead.org with esmtp (Exim 4.80.1 #2 (Red Hat Linux)) id 1W0orR-0006l2-Ia; Wed, 08 Jan 2014 08:51:37 +0000 Received: from tx2ehsobe003.messaging.microsoft.com ([65.55.88.13] helo=tx2outboundpool.messaging.microsoft.com) by merlin.infradead.org with esmtps (Exim 4.80.1 #2 (Red Hat Linux)) id 1W0orM-0006kH-3C for linux-arm-kernel@lists.infradead.org; Wed, 08 Jan 2014 08:51:34 +0000 Received: from mail190-tx2-R.bigfish.com (10.9.14.225) by TX2EHSOBE002.bigfish.com (10.9.40.22) with Microsoft SMTP Server id 14.1.225.22; Wed, 8 Jan 2014 08:51:10 +0000 Received: from mail190-tx2 (localhost [127.0.0.1]) by mail190-tx2-R.bigfish.com (Postfix) with ESMTP id 8AA4A4E0274; Wed, 8 Jan 2014 08:51:10 +0000 (UTC) X-Forefront-Antispam-Report: CIP:70.37.183.190; KIP:(null); UIP:(null); IPV:NLI; H:mail.freescale.net; RD:none; EFVD:NLI X-SpamScore: 5 X-BigFish: VS5(zz1102Ic8kzz1f42h2148h208ch1ee6h1de0h1fdah2073h2146h1202h1e76h2189h1d1ah1d2ah1fc6h1082kzz1de098h17326ah8275bh8275dh1de097h186068hz2dh2a8h839hd24he5bhf0ah1288h12a5h12a9h12bdh12e5h137ah139eh13b6h1441h1504h1537h162dh1631h1758h1898h18e1h1946h19b5h1ad9h1b0ah1b2fh2222h224fh1fb3h1d0ch1d2eh1d3fh1dc1h1dfeh1dffh1e23h1fe8h1ff5h2218h2216h226dh22d0h2327h2336h1155h) Received: from mail190-tx2 (localhost.localdomain [127.0.0.1]) by mail190-tx2 (MessageSwitch) id 138917106728564_27710; Wed, 8 Jan 2014 08:51:07 +0000 (UTC) Received: from TX2EHSMHS021.bigfish.com (unknown [10.9.14.234]) by mail190-tx2.bigfish.com (Postfix) with ESMTP id 008C76006F; Wed, 8 Jan 2014 08:51:07 +0000 (UTC) Received: from mail.freescale.net (70.37.183.190) by TX2EHSMHS021.bigfish.com (10.9.99.121) with Microsoft SMTP Server (TLS) id 14.16.227.3; Wed, 8 Jan 2014 08:51:06 +0000 Received: from tx30smr01.am.freescale.net (10.81.153.31) by 039-SN1MMR1-005.039d.mgd.msft.net (10.84.1.17) with Microsoft SMTP Server (TLS) id 14.3.158.2; Wed, 8 Jan 2014 08:51:06 +0000 Received: from ubuntu.ap.freescale.net (ubuntu-010192242118.ap.freescale.net [10.192.242.118]) by tx30smr01.am.freescale.net (8.14.3/8.14.0) with ESMTP id s088p3hG006938; Wed, 8 Jan 2014 01:51:04 -0700 From: Anson Huang To: , Subject: [PATCH V2 1/3] ARM: imx: add suspend in ocram support on i.mx6q Date: Wed, 8 Jan 2014 16:51:04 +0800 Message-ID: <1389171067-32008-1-git-send-email-b20788@freescale.com> X-Mailer: git-send-email 1.7.9.5 MIME-Version: 1.0 X-OriginatorOrg: freescale.com X-FOPE-CONNECTOR: Id%0$Dn%*$RO%0$TLS%0$FQDN%$TlsDn% X-CRM114-Version: 20100106-BlameMichelson ( TRE 0.8.0 (BSD) ) MR-646709E3 X-CRM114-CacheID: sfid-20140108_035132_356921_0ACAA67A X-CRM114-Status: GOOD ( 22.71 ) X-Spam-Score: -4.2 (----) Cc: linux-arm-kernel@lists.infradead.org X-BeenThere: linux-arm-kernel@lists.infradead.org X-Mailman-Version: 2.1.15 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Sender: "linux-arm-kernel" Errors-To: linux-arm-kernel-bounces+patchwork-linux-arm=patchwork.kernel.org@lists.infradead.org X-Spam-Status: No, score=-4.3 required=5.0 tests=BAYES_00, RCVD_IN_DNSWL_MED, RP_MATCHES_RCVD, UNPARSEABLE_RELAY autolearn=unavailable version=3.3.1 X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on mail.kernel.org X-Virus-Scanned: ClamAV using ClamSMTP When system enter suspend, we can set the DDR IO to high-Z state to save DDR IO's power, this operation can save many power(from ~26mA@1.5V to ~15mA@1.5V, measured on i.MX6Q/DL SabreSD board, R25) of DDR IO. To achieve that, we need to copy the suspend code to ocram and run the low level hardware related code(set DDR IO to high-Z) in ocram. If there is no ocram space available, then system will still do suspend in DDR, hence no DDR IO will be set to high-Z. The OCRAM usage layout is as below, ocram suspend region: ======================== high address ====================== common resume address(v7_cpu_resume) mmdc io settings v v v . . . . ^ ^ ^ imx6_suspend code PM_INFO structure(imx6_cpu_pm_info) ======================== low address ======================= Signed-off-by: Anson Huang --- Changes since V1: 1. Remove static iomap, get those necessary io address from DTS and pass them to asm code; 2. Re-organize the ARM register usage for a better reading and save some code lines. arch/arm/mach-imx/Makefile | 3 +- arch/arm/mach-imx/common.h | 4 +- arch/arm/mach-imx/hardware.h | 4 +- arch/arm/mach-imx/pm-imx6q.c | 181 ++++++++++++++- arch/arm/mach-imx/suspend-imx6.S | 473 ++++++++++++++++++++++++++++++++++++++ 5 files changed, 658 insertions(+), 7 deletions(-) create mode 100644 arch/arm/mach-imx/suspend-imx6.S diff --git a/arch/arm/mach-imx/Makefile b/arch/arm/mach-imx/Makefile index befcaf5..3d96a45 100644 --- a/arch/arm/mach-imx/Makefile +++ b/arch/arm/mach-imx/Makefile @@ -102,7 +102,8 @@ obj-$(CONFIG_SOC_IMX6Q) += clk-imx6q.o mach-imx6q.o obj-$(CONFIG_SOC_IMX6SL) += clk-imx6sl.o mach-imx6sl.o ifeq ($(CONFIG_PM),y) -obj-$(CONFIG_SOC_IMX6Q) += pm-imx6q.o headsmp.o +AFLAGS_suspend-imx6.o :=-Wa,-march=armv7-a +obj-$(CONFIG_SOC_IMX6Q) += pm-imx6q.o headsmp.o suspend-imx6.o # i.MX6SL reuses i.MX6Q code obj-$(CONFIG_SOC_IMX6SL) += pm-imx6q.o headsmp.o endif diff --git a/arch/arm/mach-imx/common.h b/arch/arm/mach-imx/common.h index 59c3b9b..9a6f265 100644 --- a/arch/arm/mach-imx/common.h +++ b/arch/arm/mach-imx/common.h @@ -1,5 +1,5 @@ /* - * Copyright 2004-2013 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright 2004-2014 Freescale Semiconductor, Inc. All Rights Reserved. */ /* @@ -145,10 +145,12 @@ void imx_cpu_die(unsigned int cpu); int imx_cpu_kill(unsigned int cpu); #ifdef CONFIG_PM +void imx6_suspend(void); void imx6q_pm_init(void); void imx6q_pm_set_ccm_base(void __iomem *base); void imx5_pm_init(void); #else +static inline void imx6_suspend(void) {} static inline void imx6q_pm_init(void) {} static inline void imx6q_pm_set_ccm_base(void __iomem *base) {} static inline void imx5_pm_init(void) {} diff --git a/arch/arm/mach-imx/hardware.h b/arch/arm/mach-imx/hardware.h index a3b0b04..abf43bb 100644 --- a/arch/arm/mach-imx/hardware.h +++ b/arch/arm/mach-imx/hardware.h @@ -1,5 +1,5 @@ /* - * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright 2004-2007, 2014 Freescale Semiconductor, Inc. All Rights Reserved. * Copyright 2008 Juergen Beisert, kernel@pengutronix.de * * This program is free software; you can redistribute it and/or @@ -20,7 +20,9 @@ #ifndef __ASM_ARCH_MXC_HARDWARE_H__ #define __ASM_ARCH_MXC_HARDWARE_H__ +#ifndef __ASSEMBLY__ #include +#endif #include #define addr_in_module(addr, mod) \ diff --git a/arch/arm/mach-imx/pm-imx6q.c b/arch/arm/mach-imx/pm-imx6q.c index 9d47adc..4ade606 100644 --- a/arch/arm/mach-imx/pm-imx6q.c +++ b/arch/arm/mach-imx/pm-imx6q.c @@ -1,5 +1,5 @@ /* - * Copyright 2011-2013 Freescale Semiconductor, Inc. + * Copyright 2011-2014 Freescale Semiconductor, Inc. * Copyright 2011 Linaro Ltd. * * The code contained herein is licensed under the GNU General Public @@ -18,12 +18,17 @@ #include #include #include +#include #include #include +#include #include +#include +#include +#include #include #include -#include +#include #include "common.h" #include "hardware.h" @@ -58,7 +63,51 @@ #define CGPR 0x64 #define BM_CGPR_CHICKEN_BIT (0x1 << 17) +#define MX6Q_SUSPEND_OCRAM_SIZE 0x1000 + static void __iomem *ccm_base; +static void *suspend_ocram_base; +static struct gen_pool *ocram_pool; +static unsigned long ocram_pbase; +static int (*imx6_suspend_in_ocram_fn)(void *ocram_vbase); + +/* + * suspend ocram space layout: + * ======================== high address ====================== + * stack for asm code to save necessary data + * v + * v + * v + * . + * . + * . + * . + * ^ + * ^ + * ^ + * imx6_suspend code + * PM_INFO structure(imx6_cpu_pm_info) + * ======================== low address ======================= + */ +/* + * This structure is for passing necessary data for low level ocram + * suspend code(arch/arm/mach-imx/suspend-imx6.S), if this struct + * definition is changed, the offset definition in + * arch/arm/mach-imx/suspend-imx6.S must be also changed accordingly, + * otherwise, the suspend to ocram fucntion will be broken! + */ +struct imx6_cpu_pm_info { + u32 cpu_type; + unsigned long pbase; + void *mmdc_addr[2]; + void *src_addr[2]; + void *iomuxc_addr[2]; + void *ccm_addr[2]; + void *gpc_addr[2]; + void *l2_addr[2]; + u32 pm_info_size; + u32 suspend_ocram_size; +}; void imx6q_set_chicken_bit(void) { @@ -177,7 +226,17 @@ int imx6q_set_lpm(enum mxc_cpu_pwr_mode mode) static int imx6q_suspend_finish(unsigned long val) { - cpu_do_idle(); + if (!imx6_suspend_in_ocram_fn) { + cpu_do_idle(); + } else { + /* + * call low level suspend function in ocram, + * as we need to float DDR IO. + */ + local_flush_tlb_all(); + imx6_suspend_in_ocram_fn(suspend_ocram_base); + } + return 0; } @@ -187,7 +246,12 @@ static int imx6q_pm_enter(suspend_state_t state) case PM_SUSPEND_MEM: imx6q_set_lpm(STOP_POWER_OFF); imx6q_enable_wb(true); - imx6q_enable_rbc(true); + /* + * For suspend into ocram, asm code already take care of + * RBC setting, so we do NOT need to do that here. + */ + if (!imx6_suspend_in_ocram_fn) + imx6q_enable_rbc(true); imx_gpc_pre_suspend(); imx_anatop_pre_suspend(); imx_set_cpu_jump(0, v7_cpu_resume); @@ -220,8 +284,117 @@ void __init imx6q_pm_set_ccm_base(void __iomem *base) void __init imx6q_pm_init(void) { + unsigned long ocram_base; + struct device_node *node; + struct platform_device *pdev; struct regmap *gpr; + void __iomem *base; + struct imx6_cpu_pm_info *pm_info; + + node = of_find_compatible_node(NULL, NULL, "mmio-sram"); + if (!node) { + pr_warn("failed to find ocram node!\n"); + goto ocram_out; + } + + pdev = of_find_device_by_node(node); + if (!pdev) { + pr_warn("failed to find ocram device!\n"); + goto ocram_out; + } + + ocram_pool = dev_get_gen_pool(&pdev->dev); + if (!ocram_pool) { + pr_warn("ocram pool unavailable!\n"); + goto ocram_out; + } + + ocram_base = gen_pool_alloc(ocram_pool, MX6Q_SUSPEND_OCRAM_SIZE); + if (!ocram_base) { + pr_warn("unable to alloc ocram!\n"); + goto ocram_out; + } + + ocram_pbase = gen_pool_virt_to_phys(ocram_pool, ocram_base); + suspend_ocram_base = __arm_ioremap(ocram_pbase, MX6Q_SUSPEND_OCRAM_SIZE, + MT_MEMORY_RWX_NONCACHED); + + pm_info = suspend_ocram_base; + pm_info->pm_info_size = sizeof(*pm_info); + pm_info->pbase = ocram_pbase; + pm_info->suspend_ocram_size = MX6Q_SUSPEND_OCRAM_SIZE; + + node = of_find_compatible_node(NULL, NULL, "fsl,imx6q-mmdc"); + if (!node) { + pr_warn("failed to find mmdc node!\n"); + goto ocram_out; + } + base = of_iomap(node, 0); + pm_info->mmdc_addr[0] = (void *)be32_to_cpu(*of_get_address(node, 0, NULL, NULL)); + pm_info->mmdc_addr[1] = base; + + node = of_find_compatible_node(NULL, NULL, "fsl,imx6q-src"); + if (!node) { + pr_warn("failed to find src node!\n"); + goto ocram_out; + } + base = of_iomap(node, 0); + pm_info->src_addr[0] = (void *)be32_to_cpu(*of_get_address(node, 0, NULL, NULL)); + pm_info->src_addr[1] = base; + + node = of_find_compatible_node(NULL, NULL, "fsl,imx6q-iomuxc"); + if (!node) { + pr_warn("failed to find iomuxc node!\n"); + goto ocram_out; + } + base = of_iomap(node, 0); + pm_info->iomuxc_addr[0] = (void *)be32_to_cpu(*of_get_address(node, 0, NULL, NULL)); + pm_info->iomuxc_addr[1] = base; + + node = of_find_compatible_node(NULL, NULL, "fsl,imx6q-ccm"); + if (!node) { + pr_warn("failed to find ccm node!\n"); + goto ocram_out; + } + pm_info->ccm_addr[0] = (void *)be32_to_cpu(*of_get_address(node, 0, NULL, NULL)); + pm_info->ccm_addr[1] = ccm_base; + + node = of_find_compatible_node(NULL, NULL, "fsl,imx6q-gpc"); + if (!node) { + pr_warn("failed to find gpc node!\n"); + goto ocram_out; + } + base = of_iomap(node, 0); + pm_info->gpc_addr[0] = (void *)be32_to_cpu(*of_get_address(node, 0, NULL, NULL)); + pm_info->gpc_addr[1] = base; + + node = of_find_compatible_node(NULL, NULL, "arm,pl310-cache"); + if (!node) { + pr_warn("failed to find pl310-cache node!\n"); + goto ocram_out; + } + base = of_iomap(node, 0); + pm_info->l2_addr[0] = (void *)be32_to_cpu(*of_get_address(node, 0, NULL, NULL)); + pm_info->l2_addr[1] = base; + + if (!pm_info->mmdc_addr[1] || !pm_info->src_addr[1] || !pm_info->iomuxc_addr[1] + || !pm_info->ccm_addr[1] || !pm_info->gpc_addr[1] || !pm_info->l2_addr[1]) { + pr_warn("failed to initialize pm_info!\n"); + goto ocram_out; + } + + /* Set cpu_type for DSM */ + if (cpu_is_imx6q()) + pm_info->cpu_type = MXC_CPU_IMX6Q; + else if (cpu_is_imx6dl()) + pm_info->cpu_type = MXC_CPU_IMX6DL; + else + pm_info->cpu_type = MXC_CPU_IMX6SL; + + imx6_suspend_in_ocram_fn = (void *)fncpy(suspend_ocram_base + sizeof(*pm_info), + &imx6_suspend, MX6Q_SUSPEND_OCRAM_SIZE - sizeof(*pm_info)); +ocram_out: WARN_ON(!ccm_base); /* diff --git a/arch/arm/mach-imx/suspend-imx6.S b/arch/arm/mach-imx/suspend-imx6.S new file mode 100644 index 0000000..be0f6bf --- /dev/null +++ b/arch/arm/mach-imx/suspend-imx6.S @@ -0,0 +1,473 @@ +/* + * Copyright 2014 Freescale Semiconductor, Inc. + * + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include +#include +#include "hardware.h" + +/* + * ==================== low level suspend ==================== + * + * Better to follow below rules to use ARM registers: + * r0: pm_info structure address; + * r1~r4: for saving pm_info members; + * r5~r9: free registers; + * r10: stack pointer for saving necessary data; + * r11: io base address. + * + * suspend ocram space layout: + * ======================== high address ====================== + * common resume address(v7_cpu_resume) + * mmdc io settings + * v + * v + * v + * . + * . + * . + * . + * ^ + * ^ + * ^ + * imx6_suspend code + * PM_INFO structure(imx6_cpu_pm_info) + * ======================== low address ======================= + */ + +/* + * Below offsets are based on struct imx6_cpu_pm_info + * which defined in arch/arm/mach-imx/pm-imx6q.c, this + * structure contains necessary pm info for low level + * suspend related code. + */ +#define PM_INFO_CPU_TYPE_OFFSET 0x0 +#define PM_INFO_PBASE_OFFSET 0x4 +#define PM_INFO_MX6Q_MMDC_P_OFFSET 0x8 +#define PM_INFO_MX6Q_MMDC_V_OFFSET 0xC +#define PM_INFO_MX6Q_SRC_P_OFFSET 0x10 +#define PM_INFO_MX6Q_SRC_V_OFFSET 0x14 +#define PM_INFO_MX6Q_IOMUXC_P_OFFSET 0x18 +#define PM_INFO_MX6Q_IOMUXC_V_OFFSET 0x1C +#define PM_INFO_MX6Q_CCM_P_OFFSET 0x20 +#define PM_INFO_MX6Q_CCM_V_OFFSET 0x24 +#define PM_INFO_MX6Q_GPC_P_OFFSET 0x28 +#define PM_INFO_MX6Q_GPC_V_OFFSET 0x2C +#define PM_INFO_MX6Q_L2_P_OFFSET 0x30 +#define PM_INFO_MX6Q_L2_V_OFFSET 0x34 +#define PM_INFO_PM_INFO_SIZE_OFFSET 0x38 +#define PM_INFO_SUSPEND_OCRAM_SIZE_OFFSET 0x3C + +#define MX6Q_SRC_GPR1 0x20 +#define MX6Q_SRC_GPR2 0x24 +#define MX6Q_MMDC_MAPSR 0x404 +#define MX6Q_GPC_IMR1 0x08 +#define MX6Q_GPC_IMR2 0x0c +#define MX6Q_GPC_IMR3 0x10 +#define MX6Q_GPC_IMR4 0x14 +#define MX6Q_CCM_CCR 0x0 + + .align 3 + + .macro imx6dq_ddr_io_save + + ldr r6, [r11, #0x5ac] /* DRAM_DQM0 */ + ldr r7, [r11, #0x5b4] /* DRAM_DQM1 */ + ldr r8, [r11, #0x528] /* DRAM_DQM2 */ + ldr r9, [r11, #0x520] /* DRAM_DQM3 */ + stmfd r10!, {r6-r9} + + ldr r6, [r11, #0x514] /* DRAM_DQM4 */ + ldr r7, [r11, #0x510] /* DRAM_DQM5 */ + ldr r8, [r11, #0x5bc] /* DRAM_DQM6 */ + ldr r9, [r11, #0x5c4] /* DRAM_DQM7 */ + stmfd r10!, {r6-r9} + + ldr r6, [r11, #0x56c] /* DRAM_CAS */ + ldr r7, [r11, #0x578] /* DRAM_RAS */ + ldr r8, [r11, #0x588] /* DRAM_SDCLK_0 */ + ldr r9, [r11, #0x594] /* DRAM_SDCLK_1 */ + stmfd r10!, {r6-r9} + + ldr r8, [r11, #0x750] /* DDRMODE_CTL */ + ldr r9, [r11, #0x774] /* DDRMODE */ + stmfd r10!, {r6-r9} + + ldr r6, [r11, #0x5a8] /* DRAM_SDQS0 */ + ldr r7, [r11, #0x5b0] /* DRAM_SDQS1 */ + ldr r8, [r11, #0x524] /* DRAM_SDQS2 */ + ldr r9, [r11, #0x51c] /* DRAM_SDQS3 */ + stmfd r10!, {r6-r9} + + ldr r6, [r11, #0x518] /* DRAM_SDQS4 */ + ldr r7, [r11, #0x50c] /* DRAM_SDQS5 */ + ldr r8, [r11, #0x5b8] /* DRAM_SDQS6 */ + ldr r9, [r11, #0x5c0] /* DRAM_SDQS7 */ + stmfd r10!, {r6-r9} + + ldr r6, [r11, #0x784] /* GPR_B0DS */ + ldr r7, [r11, #0x788] /* GPR_B1DS */ + ldr r8, [r11, #0x794] /* GPR_B2DS */ + ldr r9, [r11, #0x79c] /* GPR_B3DS */ + stmfd r10!, {r6-r9} + + ldr r6, [r11, #0x7a0] /* GPR_B4DS */ + ldr r7, [r11, #0x7a4] /* GPR_B5DS */ + ldr r8, [r11, #0x7a8] /* GPR_B6DS */ + ldr r9, [r11, #0x748] /* GPR_B7DS */ + stmfd r10!, {r6-r9} + + ldr r7, [r11, #0x74c] /* GPR_ADDS*/ + ldr r8, [r11, #0x59c] /* DRAM_SODT0*/ + ldr r9, [r11, #0x5a0] /* DRAM_SODT1*/ + stmfd r10!, {r7-r9} + + .endm + + .macro imx6dq_ddr_io_restore + + ldmea r10!, {r6-r9} + str r6, [r11, #0x5ac] /* DRAM_DQM0 */ + str r7, [r11, #0x5b4] /* DRAM_DQM1 */ + str r8, [r11, #0x528] /* DRAM_DQM2 */ + str r9, [r11, #0x520] /* DRAM_DQM3 */ + + ldmea r10!, {r6-r9} + str r6, [r11, #0x514] /* DRAM_DQM4 */ + str r7, [r11, #0x510] /* DRAM_DQM5 */ + str r8, [r11, #0x5bc] /* DRAM_DQM6 */ + str r9, [r11, #0x5c4] /* DRAM_DQM7 */ + + ldmea r10!, {r6-r9} + str r6, [r11, #0x56c] /* DRAM_CAS */ + str r7, [r11, #0x578] /* DRAM_RAS */ + str r8, [r11, #0x588] /* DRAM_SDCLK_0 */ + str r9, [r11, #0x594] /* DRAM_SDCLK_1 */ + + ldmea r10!, {r8-r9} + str r8, [r11, #0x750] /* DDRMODE_CTL */ + str r9, [r11, #0x774] /* DDRMODE */ + + ldmea r10!, {r6-r9} + str r6, [r11, #0x5a8] /* DRAM_SDQS0 */ + str r7, [r11, #0x5b0] /* DRAM_SDQS1 */ + str r8, [r11, #0x524] /* DRAM_SDQS2 */ + str r9, [r11, #0x51c] /* DRAM_SDQS3 */ + + ldmea r10!, {r6-r9} + str r6, [r11, #0x518] /* DRAM_SDQS4 */ + str r7, [r11, #0x50c] /* DRAM_SDQS5 */ + str r8, [r11, #0x5b8] /* DRAM_SDQS6 */ + str r9, [r11, #0x5c0] /* DRAM_SDQS7 */ + + ldmea r10!, {r6-r9} + str r6, [r11, #0x784] /* GPR_B0DS */ + str r7, [r11, #0x788] /* GPR_B1DS */ + str r8, [r11, #0x794] /* GPR_B2DS */ + str r9, [r11, #0x79c] /* GPR_B3DS */ + + ldmea r10!, {r6-r9} + str r6, [r11, #0x7a0] /* GPR_B4DS */ + str r7, [r11, #0x7a4] /* GPR_B5DS */ + str r8, [r11, #0x7a8] /* GPR_B6DS */ + str r9, [r11, #0x748] /* GPR_B7DS */ + + ldmea r10!, {r7-r9} + str r7, [r11, #0x74c] /* GPR_ADDS*/ + str r8, [r11, #0x59c] /* DRAM_SODT0*/ + str r9, [r11, #0x5a0] /* DRAM_SODT1*/ + + .endm + + .macro imx6dq_ddr_io_set_lpm + + mov r9, #0 + str r9, [r11, #0x5ac] /* DRAM_DQM0 */ + str r9, [r11, #0x5b4] /* DRAM_DQM1 */ + str r9, [r11, #0x528] /* DRAM_DQM2 */ + str r9, [r11, #0x520] /* DRAM_DQM3 */ + + str r9, [r11, #0x514] /* DRAM_DQM4 */ + str r9, [r11, #0x510] /* DRAM_DQM5 */ + str r9, [r11, #0x5bc] /* DRAM_DQM6 */ + str r9, [r11, #0x5c4] /* DRAM_DQM7 */ + + str r9, [r11, #0x56c] /* DRAM_CAS */ + str r9, [r11, #0x578] /* DRAM_RAS */ + str r9, [r11, #0x588] /* DRAM_SDCLK_0 */ + str r9, [r11, #0x594] /* DRAM_SDCLK_1 */ + + str r9, [r11, #0x750] /* DDRMODE_CTL */ + str r9, [r11, #0x774] /* DDRMODE */ + + str r9, [r11, #0x5a8] /* DRAM_SDQS0 */ + str r9, [r11, #0x5b0] /* DRAM_SDQS1 */ + str r9, [r11, #0x524] /* DRAM_SDQS2 */ + str r9, [r11, #0x51c] /* DRAM_SDQS3 */ + + str r9, [r11, #0x518] /* DRAM_SDQS4 */ + str r9, [r11, #0x50c] /* DRAM_SDQS5 */ + str r9, [r11, #0x5b8] /* DRAM_SDQS6 */ + str r9, [r11, #0x5c0] /* DRAM_SDQS7 */ + + str r9, [r11, #0x784] /* GPR_B0DS */ + str r9, [r11, #0x788] /* GPR_B1DS */ + str r9, [r11, #0x794] /* GPR_B2DS */ + str r9, [r11, #0x79c] /* GPR_B3DS */ + + str r9, [r11, #0x7a0] /* GPR_B4DS */ + str r9, [r11, #0x7a4] /* GPR_B5DS */ + str r9, [r11, #0x7a8] /* GPR_B6DS */ + str r9, [r11, #0x748] /* GPR_B7DS */ + + str r9, [r11, #0x74c] /* GPR_ADDS*/ + str r9, [r11, #0x59c] /* DRAM_SODT0*/ + str r9, [r11, #0x5a0] /* DRAM_SODT1*/ + + .endm + + .macro sync_l2_cache + + /* sync L2 cache to drain L2's buffers to DRAM. */ +#ifdef CONFIG_CACHE_L2X0 + ldr r11, [r0, #PM_INFO_MX6Q_L2_V_OFFSET] + mov r9, #0x0 + str r9, [r11, #L2X0_CACHE_SYNC] +1: + ldr r9, [r11, #L2X0_CACHE_SYNC] + ands r9, r9, #0x1 + bne 1b +#endif + .endm + +ENTRY(imx6_suspend) + ldr r1, [r0, #PM_INFO_CPU_TYPE_OFFSET] + ldr r2, [r0, #PM_INFO_PBASE_OFFSET] + ldr r3, [r0, #PM_INFO_PM_INFO_SIZE_OFFSET] + ldr r4, [r0, #PM_INFO_SUSPEND_OCRAM_SIZE_OFFSET] + /* + * counting the resume address in iram + * to set it in SRC register. + */ + ldr r6, =imx6_suspend + ldr r7, =resume + sub r7, r7, r6 + add r8, r2, r3 + add r9, r8, r7 + + /* + * make sure TLB contain the addr we want, + * as we will access after DDR IO floated. + */ + + ldr r11, [r0, #PM_INFO_MX6Q_CCM_V_OFFSET] + ldr r5, [r11, #0x0] + ldr r11, [r0, #PM_INFO_MX6Q_GPC_V_OFFSET] + ldr r5, [r11, #0x0] + + /* use r11 to store the IO address */ + ldr r11, [r0, #PM_INFO_MX6Q_SRC_V_OFFSET] + + /* + * read previous resume address from SRC + * register, which is v7_cpu_resume, this + * is for the jump when we finish DDR IO + * restore. + */ + ldr r5, [r11, #MX6Q_SRC_GPR1] + add r10, r0, r4 + str r9, [r11, #MX6Q_SRC_GPR1] + str r2, [r11, #MX6Q_SRC_GPR2] + + stmfd r10!, {r5} + + ldr r11, [r0, #PM_INFO_MX6Q_IOMUXC_V_OFFSET] + + cmp r1, #MXC_CPU_IMX6Q + bne ddr_io_save_dsm_done + imx6dq_ddr_io_save +ddr_io_save_dsm_done: + + /* need to sync L2 cache before DSM. */ + sync_l2_cache + + ldr r11, [r0, #PM_INFO_MX6Q_MMDC_V_OFFSET] + /* + * put DDR explicitly into self-refresh and + * disable Automatic power savings. + */ + ldr r7, [r11, #MX6Q_MMDC_MAPSR] + orr r7, r7, #0x01 + str r7, [r11, #MX6Q_MMDC_MAPSR] + + /* make the DDR explicitly enter self-refresh. */ + ldr r7, [r11, #MX6Q_MMDC_MAPSR] + orr r7, r7, #(1 << 21) + str r7, [r11, #MX6Q_MMDC_MAPSR] + +poll_dvfs_set_1: + ldr r7, [r11, #0x404] + ands r7, r7, #(1 << 25) + beq poll_dvfs_set_1 + + ldr r11, [r0, #PM_INFO_MX6Q_IOMUXC_V_OFFSET] + + cmp r1, #MXC_CPU_IMX6Q + bne ddr_io_set_lpm_dsm_done + imx6dq_ddr_io_set_lpm +ddr_io_set_lpm_dsm_done: + + /* + * mask all GPC interrupts before + * enabling the RBC counters to + * avoid the counter starting too + * early if an interupt is already + * pending. + */ + ldr r11, [r0, #PM_INFO_MX6Q_GPC_V_OFFSET] + ldr r5, [r11, #MX6Q_GPC_IMR1] + ldr r6, [r11, #MX6Q_GPC_IMR2] + ldr r7, [r11, #MX6Q_GPC_IMR3] + ldr r8, [r11, #MX6Q_GPC_IMR4] + + ldr r9, =0xffffffff + str r9, [r11, #MX6Q_GPC_IMR1] + str r9, [r11, #MX6Q_GPC_IMR2] + str r9, [r11, #MX6Q_GPC_IMR3] + str r9, [r11, #MX6Q_GPC_IMR4] + + /* + * enable the RBC bypass counter here + * to hold off the interrupts. RBC counter + * = 32 (1ms), Minimum RBC delay should be + * 400us for the analog LDOs to power down. + */ + ldr r11, [r0, #PM_INFO_MX6Q_CCM_V_OFFSET] + ldr r9, [r11, #MX6Q_CCM_CCR] + bic r9, r9, #(0x3f << 21) + orr r9, r9, #(0x20 << 21) + str r9, [r11, #MX6Q_CCM_CCR] + + /* enable the counter. */ + ldr r9, [r11, #MX6Q_CCM_CCR] + orr r9, r9, #(0x1 << 27) + str r9, [r11, #MX6Q_CCM_CCR] + + /* unmask all the GPC interrupts. */ + ldr r11, [r0, #PM_INFO_MX6Q_GPC_V_OFFSET] + str r5, [r11, #MX6Q_GPC_IMR1] + str r6, [r11, #MX6Q_GPC_IMR2] + str r7, [r11, #MX6Q_GPC_IMR3] + str r8, [r11, #MX6Q_GPC_IMR4] + + /* + * now delay for a short while (3usec) + * ARM is at 1GHz at this point + * so a short loop should be enough. + * this delay is required to ensure that + * the RBC counter can start counting in + * case an interrupt is already pending + * or in case an interrupt arrives just + * as ARM is about to assert DSM_request. + */ + ldr r5, =2000 +rbc_loop: + sub r5, r5, #0x1 + cmp r5, #0x0 + bne rbc_loop + + /* Zzz, enter stop mode */ + wfi + nop + nop + nop + nop + + /* + * run to here means there is pending + * wakeup source, system should auto + * resume, we need to restore DDR IO first + */ + + add r10, r0, r4 + /* skip the lr saved in iram */ + sub r10, r10, #0x4 + + ldr r11, [r0, #PM_INFO_MX6Q_IOMUXC_V_OFFSET] + + cmp r1, #MXC_CPU_IMX6Q + bne ddr_io_restore_done + imx6dq_ddr_io_restore +ddr_io_restore_done: + + ldr r11, [r0, #PM_INFO_MX6Q_MMDC_V_OFFSET] + /* let DDR out of self-refresh. */ + ldr r7, [r11, #MX6Q_MMDC_MAPSR] + bic r7, r7, #(1 << 21) + str r7, [r11, #MX6Q_MMDC_MAPSR] + +poll_dvfs_clear_2: + ldr r7, [r11, #MX6Q_MMDC_MAPSR] + ands r7, r7, #(1 << 25) + bne poll_dvfs_clear_2 + /* enable DDR auto power saving */ + ldr r7, [r11, #MX6Q_MMDC_MAPSR] + bic r7, r7, #0x1 + str r7, [r11, #MX6Q_MMDC_MAPSR] + /* return to suspend finish */ + mov pc, lr + +resume: + /* invalidate L1 I-cache first */ + mov r5, #0x0 + mcr p15, 0, r5, c7, c5, 0 + mcr p15, 0, r5, c7, c5, 0 + mcr p15, 0, r5, c7, c5, 6 + /* enable the Icache and branch prediction */ + mov r5, #0x1800 + mcr p15, 0, r5, c1, c0, 0 + isb + + ldr r4, [r0, #PM_INFO_SUSPEND_OCRAM_SIZE_OFFSET] + add r10, r0, r4 + ldmea r10!, {lr} + + /* get cpu type */ + ldr r1, [r0, #PM_INFO_CPU_TYPE_OFFSET] + + /* clear core0's entry and parameter */ + ldr r11, [r0, #PM_INFO_MX6Q_SRC_P_OFFSET] + mov r7, #0 + str r7, [r11, #MX6Q_SRC_GPR1] + str r7, [r11, #MX6Q_SRC_GPR2] + + ldr r11, [r0, #PM_INFO_MX6Q_IOMUXC_P_OFFSET] + + cmp r1, #MXC_CPU_IMX6Q + bne ddr_io_restore_dsm_done + imx6dq_ddr_io_restore +ddr_io_restore_dsm_done: + + ldr r11, [r0, #PM_INFO_MX6Q_MMDC_P_OFFSET] + /* let DDR out of self-refresh */ + ldr r7, [r11, #MX6Q_MMDC_MAPSR] + bic r7, r7, #(1 << 21) + str r7, [r11, #MX6Q_MMDC_MAPSR] + +poll_dvfs_clear_1: + ldr r7, [r11, #MX6Q_MMDC_MAPSR] + ands r7, r7, #(1 << 25) + bne poll_dvfs_clear_1 + /* enable DDR auto power saving */ + ldr r7, [r11, #MX6Q_MMDC_MAPSR] + bic r7, r7, #0x1 + str r7, [r11, #MX6Q_MMDC_MAPSR] + mov pc, lr +ENDPROC(imx6_suspend)