[PATCH] ARM: imx53: Set DDR pins to high impedance when in suspend to RAM.
Shawn Guo
shawn.guo at linaro.org
Thu May 7 04:14:43 PDT 2015
On Tue, Apr 28, 2015 at 05:44:58PM +0200, Martin Fuzzey wrote:
> In order to save power the DDR pins should be put into high
> impedance when in suspend to RAM.
>
> This requires manually requesting self refresh (rather than using the
> automatic mode implemented by the CCM / ESDCTL), followed by
> reconfiguring the IOMUXC.
>
> Of course the code to do this cannot itself run from DDR so the
> code is copied to and executed from internal memory.
>
> In my tests using a custom i.MX53 board with LPDDR2 RAM
> this reduced the suspend power consumption from 200mW to 60mW.
>
> Signed-off-by: Martin Fuzzey <mfuzzey at parkeon.com>
Nice patch. I tested it on my i.mx51 and i.mx53 boards and suspend
still works. A couple of comments below though.
> ---
> arch/arm/mach-imx/Makefile | 1
> arch/arm/mach-imx/common.h | 4 +
> arch/arm/mach-imx/pm-imx5.c | 205 +++++++++++++++++++++++++++++++++++++
> arch/arm/mach-imx/suspend-imx53.S | 139 +++++++++++++++++++++++++
> 4 files changed, 348 insertions(+), 1 deletion(-)
> create mode 100644 arch/arm/mach-imx/suspend-imx53.S
>
> diff --git a/arch/arm/mach-imx/Makefile b/arch/arm/mach-imx/Makefile
> index f5ac685..984bee2 100644
> --- a/arch/arm/mach-imx/Makefile
> +++ b/arch/arm/mach-imx/Makefile
> @@ -101,6 +101,7 @@ obj-$(CONFIG_SOC_IMX6SX) += clk-imx6sx.o mach-imx6sx.o
> ifeq ($(CONFIG_SUSPEND),y)
> AFLAGS_suspend-imx6.o :=-Wa,-march=armv7-a
> obj-$(CONFIG_SOC_IMX6) += suspend-imx6.o
> +obj-$(CONFIG_SOC_IMX53) += suspend-imx53.o
> endif
> obj-$(CONFIG_SOC_IMX6) += pm-imx6.o
>
> diff --git a/arch/arm/mach-imx/common.h b/arch/arm/mach-imx/common.h
> index cfcdb62..0bd9d82 100644
> --- a/arch/arm/mach-imx/common.h
> +++ b/arch/arm/mach-imx/common.h
> @@ -122,9 +122,13 @@ int imx_cpu_kill(unsigned int cpu);
>
> #ifdef CONFIG_SUSPEND
> void v7_cpu_resume(void);
> +void imx53_suspend(void __iomem *ocram_vbase);
> +extern const u32 imx53_suspend_sz;
> void imx6_suspend(void __iomem *ocram_vbase);
> #else
> static inline void v7_cpu_resume(void) {}
> +static inline void imx53_suspend(void __iomem *ocram_vbase) {}
> +static const u32 imx53_suspend_sz;
> static inline void imx6_suspend(void __iomem *ocram_vbase) {}
> #endif
>
> diff --git a/arch/arm/mach-imx/pm-imx5.c b/arch/arm/mach-imx/pm-imx5.c
> index f1f80ab..9572f3b 100644
> --- a/arch/arm/mach-imx/pm-imx5.c
> +++ b/arch/arm/mach-imx/pm-imx5.c
> @@ -13,7 +13,14 @@
> #include <linux/io.h>
> #include <linux/err.h>
> #include <linux/export.h>
> +
> +#include <linux/genalloc.h>
> +#include <linux/of.h>
> +#include <linux/of_address.h>
> +#include <linux/of_platform.h>
> +
> #include <asm/cacheflush.h>
> +#include <asm/fncpy.h>
> #include <asm/system_misc.h>
> #include <asm/tlbflush.h>
>
> @@ -49,9 +56,48 @@
> */
> #define IMX5_DEFAULT_CPU_IDLE_STATE WAIT_UNCLOCKED_POWER_OFF
>
> +struct imx5_suspend_io_config {
> + u32 offset;
> + u32 clear;
> + u32 set;
> +};
> +
> struct imx5_pm_data {
> phys_addr_t cortex_addr;
> phys_addr_t gpc_addr;
> + phys_addr_t m4if_addr;
> + phys_addr_t iomuxc_addr;
> + void (*suspend_asm)(void __iomem *ocram_vbase);
> + const u32 *suspend_asm_sz;
> + const struct imx5_suspend_io_config *suspend_io_config;
> + int suspend_io_count;
> +};
> +
> +static const struct imx5_suspend_io_config imx53_suspend_io_config[] = {
> +#define MX53_DSE_HIGHZ_MASK (0x7 << 19)
> + {.offset = 0x584, .clear = MX53_DSE_HIGHZ_MASK}, /* DQM0 */
> + {.offset = 0x594, .clear = MX53_DSE_HIGHZ_MASK}, /* DQM1 */
> + {.offset = 0x560, .clear = MX53_DSE_HIGHZ_MASK}, /* DQM2 */
> + {.offset = 0x554, .clear = MX53_DSE_HIGHZ_MASK}, /* DQM3 */
> + {.offset = 0x574, .clear = MX53_DSE_HIGHZ_MASK}, /* CAS */
> + {.offset = 0x588, .clear = MX53_DSE_HIGHZ_MASK}, /* RAS */
> + {.offset = 0x578, .clear = MX53_DSE_HIGHZ_MASK}, /* SDCLK_0 */
> + {.offset = 0x570, .clear = MX53_DSE_HIGHZ_MASK}, /* SDCLK_1 */
> +
> + {.offset = 0x580, .clear = MX53_DSE_HIGHZ_MASK}, /* SDODT0 */
> + {.offset = 0x564, .clear = MX53_DSE_HIGHZ_MASK}, /* SDODT1 */
> + {.offset = 0x57c, .clear = MX53_DSE_HIGHZ_MASK}, /* SDQS0 */
> + {.offset = 0x590, .clear = MX53_DSE_HIGHZ_MASK}, /* SDQS1 */
> + {.offset = 0x568, .clear = MX53_DSE_HIGHZ_MASK}, /* SDQS2 */
> + {.offset = 0x558, .clear = MX53_DSE_HIGHZ_MASK}, /* SDSQ3 */
> + {.offset = 0x6f0, .clear = MX53_DSE_HIGHZ_MASK}, /* GRP_ADDS */
> + {.offset = 0x718, .clear = MX53_DSE_HIGHZ_MASK}, /* GRP_BODS */
> + {.offset = 0x71c, .clear = MX53_DSE_HIGHZ_MASK}, /* GRP_B1DS */
> + {.offset = 0x728, .clear = MX53_DSE_HIGHZ_MASK}, /* GRP_B2DS */
> + {.offset = 0x72c, .clear = MX53_DSE_HIGHZ_MASK}, /* GRP_B3DS */
> +
> + /* Controls the CKE signal which is required to leave self refresh */
> + {.offset = 0x720, .clear = MX53_DSE_HIGHZ_MASK, .set = 1 << 19}, /* CTLDS */
> };
>
> static const struct imx5_pm_data imx51_pm_data __initconst = {
> @@ -62,11 +108,42 @@ static const struct imx5_pm_data imx51_pm_data __initconst = {
> static const struct imx5_pm_data imx53_pm_data __initconst = {
> .cortex_addr = 0x63fa0000,
> .gpc_addr = 0x53fd8000,
> + .m4if_addr = 0x63fd8000,
> + .iomuxc_addr = 0x53fa8000,
> + .suspend_asm = &imx53_suspend,
> + .suspend_asm_sz = &imx53_suspend_sz,
> + .suspend_io_config = imx53_suspend_io_config,
> + .suspend_io_count = ARRAY_SIZE(imx53_suspend_io_config),
> +};
> +
> +#define MX5_MAX_SUSPEND_IOCONFIG ARRAY_SIZE(imx53_suspend_io_config)
> +
> +struct imx5_suspend_io_state {
> + u32 offset;
> + u32 clear;
> + u32 set;
> + u32 saved_value;
> };
imx5_suspend_io_config and imx5_suspend_io_state are a bit redundant.
Can we drop imx5_suspend_io_config structure and define these static
offset/clear/set date as an array of imx5_suspend_io_state directly?
The bonus points are that we can save one data structure, and the
suspend_info->io_state array in IRAM can be assigned with a simple
memcpy() call.
>
> +/*
> + * This structure is for passing necessary data for low level ocram
> + * suspend code(arch/arm/mach-imx/suspend-imx53.S), if this struct
> + * definition is changed, the offset definition in that file
> + * must be also changed accordingly otherwise, the suspend to ocram
> + * function will be broken!
> + */
> +struct imx5_cpu_suspend_info {
> + void __iomem *m4if_base;
> + void __iomem *iomuxc_base;
> + u32 io_count;
> + struct imx5_suspend_io_state io_state[MX5_MAX_SUSPEND_IOCONFIG];
> +} __aligned(8);
> +
> static void __iomem *ccm_base;
> static void __iomem *cortex_base;
> static void __iomem *gpc_base;
> +static void __iomem *suspend_ocram_base;
> +static void (*imx5_suspend_in_ocram_fn)(void __iomem *ocram_vbase);
>
> void __init imx5_pm_set_ccm_base(void __iomem *base)
> {
> @@ -161,8 +238,15 @@ static int mx5_suspend_enter(suspend_state_t state)
> /*clear the EMPGC0/1 bits */
> __raw_writel(0, gpc_base + MXC_SRPG_EMPGC0_SRPGCR);
> __raw_writel(0, gpc_base + MXC_SRPG_EMPGC1_SRPGCR);
> +
> + if (imx5_suspend_in_ocram_fn)
> + imx5_suspend_in_ocram_fn(suspend_ocram_base);
> + else
> + cpu_do_idle();
> +
> + } else {
> + cpu_do_idle();
> }
> - cpu_do_idle();
>
> /* return registers to default idle state */
> mx5_cpu_lp_set(IMX5_DEFAULT_CPU_IDLE_STATE);
> @@ -194,6 +278,120 @@ static void imx5_pm_idle(void)
> imx5_cpu_do_idle();
> }
>
> +static int __init imx_suspend_alloc_ocram(
> + size_t size,
> + void __iomem **virt_out,
> + phys_addr_t *phys_out)
> +{
> + struct device_node *node;
> + struct platform_device *pdev;
> + struct gen_pool *ocram_pool;
> + unsigned long ocram_base;
> + void __iomem *virt;
> + phys_addr_t phys;
> + int ret = 0;
> +
> + /* Copied from imx6: TODO factorize */
I'm not asking you to do it, but just reminding that this function has
nothing specific to i.MX. So consolidation should be done in sram core
level.
Shawn
> + node = of_find_compatible_node(NULL, NULL, "mmio-sram");
> + if (!node) {
> + pr_warn("%s: failed to find ocram node!\n", __func__);
> + return -ENODEV;
> + }
> +
> + pdev = of_find_device_by_node(node);
> + if (!pdev) {
> + pr_warn("%s: failed to find ocram device!\n", __func__);
> + ret = -ENODEV;
> + goto put_node;
> + }
> +
> + ocram_pool = dev_get_gen_pool(&pdev->dev);
> + if (!ocram_pool) {
> + pr_warn("%s: ocram pool unavailable!\n", __func__);
> + ret = -ENODEV;
> + goto put_node;
> + }
> +
> + ocram_base = gen_pool_alloc(ocram_pool, size);
> + if (!ocram_base) {
> + pr_warn("%s: unable to alloc ocram!\n", __func__);
> + ret = -ENOMEM;
> + goto put_node;
> + }
> +
> + phys = gen_pool_virt_to_phys(ocram_pool, ocram_base);
> + virt = __arm_ioremap_exec(phys, size, false);
> + if (phys_out)
> + *phys_out = phys;
> + if (virt_out)
> + *virt_out = virt;
> +
> +put_node:
> + of_node_put(node);
> +
> + return ret;
> +}
> +
> +static int __init imx5_suspend_init(const struct imx5_pm_data *soc_data)
> +{
> + struct imx5_cpu_suspend_info *suspend_info;
> + const struct imx5_suspend_io_config *io_config;
> + struct imx5_suspend_io_state *io_state;
> + int i;
> + int ret;
> + /* Need this to avoid compile error due to const typeof in fncpy.h */
> + void (*suspend_asm)(void __iomem *) = soc_data->suspend_asm;
> +
> + if (!suspend_asm)
> + return 0;
> +
> + if (!soc_data->suspend_asm_sz || !*soc_data->suspend_asm_sz)
> + return -EINVAL;
> +
> + ret = imx_suspend_alloc_ocram(
> + *soc_data->suspend_asm_sz + sizeof(*suspend_info),
> + &suspend_ocram_base, NULL);
> + if (ret)
> + return ret;
> +
> + suspend_info = suspend_ocram_base;
> +
> + suspend_info->io_count = soc_data->suspend_io_count;
> + io_config = soc_data->suspend_io_config;
> + io_state = suspend_info->io_state;
> + for (i = 0; i < soc_data->suspend_io_count;
> + i++, io_config++, io_state++) {
> + io_state->offset = io_config->offset;
> + io_state->clear = io_config->clear;
> + io_state->set = io_config->set;
> + }
> +
> + suspend_info->m4if_base = ioremap(soc_data->m4if_addr, SZ_16K);
> + if (!suspend_info->m4if_base) {
> + ret = -ENOMEM;
> + goto failed_map_m4if;
> + }
> +
> + suspend_info->iomuxc_base = ioremap(soc_data->iomuxc_addr, SZ_16K);
> + if (!suspend_info->iomuxc_base) {
> + ret = -ENOMEM;
> + goto failed_map_iomuxc;
> + }
> +
> + imx5_suspend_in_ocram_fn = fncpy(
> + suspend_ocram_base + sizeof(*suspend_info),
> + suspend_asm,
> + *soc_data->suspend_asm_sz);
> +
> + return 0;
> +
> +failed_map_iomuxc:
> + iounmap(suspend_info->m4if_base);
> +
> +failed_map_m4if:
> + return ret;
> +}
> +
> static int __init imx5_pm_common_init(const struct imx5_pm_data *data)
> {
> int ret;
> @@ -219,6 +417,11 @@ static int __init imx5_pm_common_init(const struct imx5_pm_data *data)
> if (ret)
> pr_warn("%s: cpuidle init failed %d\n", __func__, ret);
>
> + ret = imx5_suspend_init(data);
> + if (ret)
> + pr_warn("%s: No DDR LPM support with suspend %d!\n",
> + __func__, ret);
> +
> suspend_set_ops(&mx5_suspend_ops);
>
> return 0;
> diff --git a/arch/arm/mach-imx/suspend-imx53.S b/arch/arm/mach-imx/suspend-imx53.S
> new file mode 100644
> index 0000000..5ed078a
> --- /dev/null
> +++ b/arch/arm/mach-imx/suspend-imx53.S
> @@ -0,0 +1,139 @@
> +/*
> + * Copyright (C) 2008-2011 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 <linux/linkage.h>
> +
> +#define M4IF_MCR0_OFFSET (0x008C)
> +#define M4IF_MCR0_FDVFS (0x1 << 11)
> +#define M4IF_MCR0_FDVACK (0x1 << 27)
> +
> + .align 3
> +
> +/*
> + * ==================== low level suspend ====================
> + *
> + * On entry
> + * r0: pm_info structure address;
> + *
> + * suspend ocram space layout:
> + * ======================== high address ======================
> + * .
> + * .
> + * .
> + * ^
> + * ^
> + * ^
> + * imx53_suspend code
> + * PM_INFO structure(imx53_suspend_info)
> + * ======================== low address =======================
> + */
> +
> +/* Offsets of members of struct imx53_suspend_info */
> +#define SUSPEND_INFO_MX53_M4IF_V_OFFSET 0x0
> +#define SUSPEND_INFO_MX53_IOMUXC_V_OFFSET 0x4
> +#define SUSPEND_INFO_MX53_IO_COUNT_OFFSET 0x8
> +#define SUSPEND_INFO_MX53_IO_STATE_OFFSET 0xc
> +
> +ENTRY(imx53_suspend)
> + stmfd sp!, {r4,r5,r6,r7}
> +
> + /* Save pad config */
> + ldr r1, [r0, #SUSPEND_INFO_MX53_IO_COUNT_OFFSET]
> + cmp r1, #0
> + beq skip_pad_conf_1
> +
> + add r2, r0, #SUSPEND_INFO_MX53_IO_STATE_OFFSET
> + ldr r3, [r0, #SUSPEND_INFO_MX53_IOMUXC_V_OFFSET]
> +
> +1:
> + ldr r5, [r2], #12 /* IOMUXC register offset */
> + ldr r6, [r3, r5] /* current value */
> + str r6, [r2], #4 /* save area */
> + subs r1, r1, #1
> + bne 1b
> +
> +skip_pad_conf_1:
> + /* Set FDVFS bit of M4IF_MCR0 to request DDR to enter self-refresh */
> + ldr r1, [r0, #SUSPEND_INFO_MX53_M4IF_V_OFFSET]
> + ldr r2,[r1, #M4IF_MCR0_OFFSET]
> + orr r2, r2, #M4IF_MCR0_FDVFS
> + str r2,[r1, #M4IF_MCR0_OFFSET]
> +
> + /* Poll FDVACK bit of M4IF_MCR to wait for DDR to enter self-refresh */
> +wait_sr_ack:
> + ldr r2,[r1, #M4IF_MCR0_OFFSET]
> + ands r2, r2, #M4IF_MCR0_FDVACK
> + beq wait_sr_ack
> +
> + /* Set pad config */
> + ldr r1, [r0, #SUSPEND_INFO_MX53_IO_COUNT_OFFSET]
> + cmp r1, #0
> + beq skip_pad_conf_2
> +
> + add r2, r0, #SUSPEND_INFO_MX53_IO_STATE_OFFSET
> + ldr r3, [r0, #SUSPEND_INFO_MX53_IOMUXC_V_OFFSET]
> +
> +2:
> + ldr r5, [r2], #4 /* IOMUXC register offset */
> + ldr r6, [r2], #4 /* clear */
> + ldr r7, [r3, r5]
> + bic r7, r7, r6
> + ldr r6, [r2], #8 /* set */
> + orr r7, r7, r6
> + str r7, [r3, r5]
> + subs r1, r1, #1
> + bne 2b
> +
> +skip_pad_conf_2:
> + /* Zzz, enter stop mode */
> + wfi
> + nop
> + nop
> + nop
> + nop
> +
> + /* Restore pad config */
> + ldr r1, [r0, #SUSPEND_INFO_MX53_IO_COUNT_OFFSET]
> + cmp r1, #0
> + beq skip_pad_conf_3
> +
> + add r2, r0, #SUSPEND_INFO_MX53_IO_STATE_OFFSET
> + ldr r3, [r0, #SUSPEND_INFO_MX53_IOMUXC_V_OFFSET]
> +
> +3:
> + ldr r5, [r2], #12 /* IOMUXC register offset */
> + ldr r6, [r2], #4 /* saved value */
> + str r6, [r3, r5]
> + subs r1, r1, #1
> + bne 3b
> +
> +skip_pad_conf_3:
> + /* Clear FDVFS bit of M4IF_MCR0 to request DDR to exit self-refresh */
> + ldr r1, [r0, #SUSPEND_INFO_MX53_M4IF_V_OFFSET]
> + ldr r2,[r1, #M4IF_MCR0_OFFSET]
> + bic r2, r2, #M4IF_MCR0_FDVFS
> + str r2,[r1, #M4IF_MCR0_OFFSET]
> +
> + /* Poll FDVACK bit of M4IF_MCR to wait for DDR to exit self-refresh */
> +wait_ar_ack:
> + ldr r2,[r1, #M4IF_MCR0_OFFSET]
> + ands r2, r2, #M4IF_MCR0_FDVACK
> + bne wait_ar_ack
> +
> + /* Restore registers */
> + ldmfd sp!, {r4,r5,r6,r7}
> + mov pc, lr
> +
> +ENDPROC(imx53_suspend)
> +
> +ENTRY(imx53_suspend_sz)
> + .word . - imx53_suspend
>
More information about the linux-arm-kernel
mailing list