/*
 * 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
