| /* | 
 |  * 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 <linux/linkage.h> | 
 | #include <asm/assembler.h> | 
 | #include <asm/asm-offsets.h> | 
 | #include <asm/hardware/cache-l2x0.h> | 
 | #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 ~ r10: free registers; | 
 |  * r11: io base address. | 
 |  * | 
 |  * suspend ocram space layout: | 
 |  * ======================== high address ====================== | 
 |  *                              . | 
 |  *                              . | 
 |  *                              . | 
 |  *                              ^ | 
 |  *                              ^ | 
 |  *                              ^ | 
 |  *                      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_PBASE_OFFSET			0x0 | 
 | #define PM_INFO_RESUME_ADDR_OFFSET		0x4 | 
 | #define PM_INFO_DDR_TYPE_OFFSET			0x8 | 
 | #define PM_INFO_PM_INFO_SIZE_OFFSET		0xC | 
 | #define PM_INFO_MX6Q_MMDC_P_OFFSET		0x10 | 
 | #define PM_INFO_MX6Q_MMDC_V_OFFSET		0x14 | 
 | #define PM_INFO_MX6Q_SRC_P_OFFSET		0x18 | 
 | #define PM_INFO_MX6Q_SRC_V_OFFSET		0x1C | 
 | #define PM_INFO_MX6Q_IOMUXC_P_OFFSET		0x20 | 
 | #define PM_INFO_MX6Q_IOMUXC_V_OFFSET		0x24 | 
 | #define PM_INFO_MX6Q_CCM_P_OFFSET		0x28 | 
 | #define PM_INFO_MX6Q_CCM_V_OFFSET		0x2C | 
 | #define PM_INFO_MX6Q_GPC_P_OFFSET		0x30 | 
 | #define PM_INFO_MX6Q_GPC_V_OFFSET		0x34 | 
 | #define PM_INFO_MX6Q_L2_P_OFFSET		0x38 | 
 | #define PM_INFO_MX6Q_L2_V_OFFSET		0x3C | 
 | #define PM_INFO_MMDC_IO_NUM_OFFSET		0x40 | 
 | #define PM_INFO_MMDC_IO_VAL_OFFSET		0x44 | 
 |  | 
 | #define MX6Q_SRC_GPR1	0x20 | 
 | #define MX6Q_SRC_GPR2	0x24 | 
 | #define MX6Q_MMDC_MAPSR	0x404 | 
 | #define MX6Q_MMDC_MPDGCTRL0	0x83c | 
 | #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  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] | 
 | 	teq	r11, #0 | 
 | 	beq	6f | 
 | 	mov	r6, #0x0 | 
 | 	str	r6, [r11, #L2X0_CACHE_SYNC] | 
 | 1: | 
 | 	ldr	r6, [r11, #L2X0_CACHE_SYNC] | 
 | 	ands	r6, r6, #0x1 | 
 | 	bne	1b | 
 | 6: | 
 | #endif | 
 |  | 
 | 	.endm | 
 |  | 
 | 	.macro	resume_mmdc | 
 |  | 
 | 	/* restore MMDC IO */ | 
 | 	cmp	r5, #0x0 | 
 | 	ldreq	r11, [r0, #PM_INFO_MX6Q_IOMUXC_V_OFFSET] | 
 | 	ldrne	r11, [r0, #PM_INFO_MX6Q_IOMUXC_P_OFFSET] | 
 |  | 
 | 	ldr	r6, [r0, #PM_INFO_MMDC_IO_NUM_OFFSET] | 
 | 	ldr	r7, =PM_INFO_MMDC_IO_VAL_OFFSET | 
 | 	add	r7, r7, r0 | 
 | 1: | 
 | 	ldr	r8, [r7], #0x4 | 
 | 	ldr	r9, [r7], #0x4 | 
 | 	str	r9, [r11, r8] | 
 | 	subs	r6, r6, #0x1 | 
 | 	bne	1b | 
 |  | 
 | 	cmp	r5, #0x0 | 
 | 	ldreq	r11, [r0, #PM_INFO_MX6Q_MMDC_V_OFFSET] | 
 | 	ldrne	r11, [r0, #PM_INFO_MX6Q_MMDC_P_OFFSET] | 
 |  | 
 | 	cmp	r3, #IMX_DDR_TYPE_LPDDR2 | 
 | 	bne	4f | 
 |  | 
 | 	/* reset read FIFO, RST_RD_FIFO */ | 
 | 	ldr	r7, =MX6Q_MMDC_MPDGCTRL0 | 
 | 	ldr	r6, [r11, r7] | 
 | 	orr     r6, r6, #(1 << 31) | 
 | 	str	r6, [r11, r7] | 
 | 2: | 
 | 	ldr	r6, [r11, r7] | 
 | 	ands	r6, r6, #(1 << 31) | 
 | 	bne	2b | 
 |  | 
 | 	/* reset FIFO a second time */ | 
 | 	ldr	r6, [r11, r7] | 
 | 	orr     r6, r6, #(1 << 31) | 
 | 	str	r6, [r11, r7] | 
 | 3: | 
 | 	ldr	r6, [r11, r7] | 
 | 	ands	r6, r6, #(1 << 31) | 
 | 	bne	3b | 
 | 4: | 
 | 	/* let DDR out of self-refresh */ | 
 | 	ldr	r7, [r11, #MX6Q_MMDC_MAPSR] | 
 | 	bic	r7, r7, #(1 << 21) | 
 | 	str	r7, [r11, #MX6Q_MMDC_MAPSR] | 
 | 5: | 
 | 	ldr	r7, [r11, #MX6Q_MMDC_MAPSR] | 
 | 	ands	r7, r7, #(1 << 25) | 
 | 	bne	5b | 
 |  | 
 | 	/* enable DDR auto power saving */ | 
 | 	ldr	r7, [r11, #MX6Q_MMDC_MAPSR] | 
 | 	bic	r7, r7, #0x1 | 
 | 	str	r7, [r11, #MX6Q_MMDC_MAPSR] | 
 |  | 
 | 	.endm | 
 |  | 
 | ENTRY(imx6_suspend) | 
 | 	ldr	r1, [r0, #PM_INFO_PBASE_OFFSET] | 
 | 	ldr	r2, [r0, #PM_INFO_RESUME_ADDR_OFFSET] | 
 | 	ldr	r3, [r0, #PM_INFO_DDR_TYPE_OFFSET] | 
 | 	ldr	r4, [r0, #PM_INFO_PM_INFO_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, r1, r4 | 
 | 	add	r9, r8, r7 | 
 |  | 
 | 	/* | 
 | 	 * make sure TLB contain the addr we want, | 
 | 	 * as we will access them after MMDC IO floated. | 
 | 	 */ | 
 |  | 
 | 	ldr	r11, [r0, #PM_INFO_MX6Q_CCM_V_OFFSET] | 
 | 	ldr	r6, [r11, #0x0] | 
 | 	ldr	r11, [r0, #PM_INFO_MX6Q_GPC_V_OFFSET] | 
 | 	ldr	r6, [r11, #0x0] | 
 | 	ldr	r11, [r0, #PM_INFO_MX6Q_IOMUXC_V_OFFSET] | 
 | 	ldr	r6, [r11, #0x0] | 
 |  | 
 | 	/* use r11 to store the IO address */ | 
 | 	ldr	r11, [r0, #PM_INFO_MX6Q_SRC_V_OFFSET] | 
 | 	/* store physical resume addr and pm_info address. */ | 
 | 	str	r9, [r11, #MX6Q_SRC_GPR1] | 
 | 	str	r1, [r11, #MX6Q_SRC_GPR2] | 
 |  | 
 | 	/* 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, #0x1 | 
 | 	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: | 
 | 	ldr	r7, [r11, #MX6Q_MMDC_MAPSR] | 
 | 	ands	r7, r7, #(1 << 25) | 
 | 	beq	poll_dvfs_set | 
 |  | 
 | 	ldr	r11, [r0, #PM_INFO_MX6Q_IOMUXC_V_OFFSET] | 
 | 	ldr	r6, =0x0 | 
 | 	ldr	r7, [r0, #PM_INFO_MMDC_IO_NUM_OFFSET] | 
 | 	ldr	r8, =PM_INFO_MMDC_IO_VAL_OFFSET | 
 | 	add	r8, r8, r0 | 
 | 	/* LPDDR2's last 3 IOs need special setting */ | 
 | 	cmp	r3, #IMX_DDR_TYPE_LPDDR2 | 
 | 	subeq	r7, r7, #0x3 | 
 | set_mmdc_io_lpm: | 
 | 	ldr	r9, [r8], #0x8 | 
 | 	str	r6, [r11, r9] | 
 | 	subs	r7, r7, #0x1 | 
 | 	bne	set_mmdc_io_lpm | 
 |  | 
 | 	cmp 	r3, #IMX_DDR_TYPE_LPDDR2 | 
 | 	bne	set_mmdc_io_lpm_done | 
 | 	ldr	r6, =0x1000 | 
 | 	ldr	r9, [r8], #0x8 | 
 | 	str	r6, [r11, r9] | 
 | 	ldr	r9, [r8], #0x8 | 
 | 	str	r6, [r11, r9] | 
 | 	ldr	r6, =0x80000 | 
 | 	ldr	r9, [r8] | 
 | 	str	r6, [r11, r9] | 
 | set_mmdc_io_lpm_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	r6, [r11, #MX6Q_GPC_IMR1] | 
 | 	ldr	r7, [r11, #MX6Q_GPC_IMR2] | 
 | 	ldr	r8, [r11, #MX6Q_GPC_IMR3] | 
 | 	ldr	r9, [r11, #MX6Q_GPC_IMR4] | 
 |  | 
 | 	ldr	r10, =0xffffffff | 
 | 	str	r10, [r11, #MX6Q_GPC_IMR1] | 
 | 	str	r10, [r11, #MX6Q_GPC_IMR2] | 
 | 	str	r10, [r11, #MX6Q_GPC_IMR3] | 
 | 	str	r10, [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	r10, [r11, #MX6Q_CCM_CCR] | 
 | 	bic	r10, r10, #(0x3f << 21) | 
 | 	orr	r10, r10, #(0x20 << 21) | 
 | 	str	r10, [r11, #MX6Q_CCM_CCR] | 
 |  | 
 | 	/* enable the counter. */ | 
 | 	ldr	r10, [r11, #MX6Q_CCM_CCR] | 
 | 	orr	r10, r10, #(0x1 << 27) | 
 | 	str	r10, [r11, #MX6Q_CCM_CCR] | 
 |  | 
 | 	/* unmask all the GPC interrupts. */ | 
 | 	ldr	r11, [r0, #PM_INFO_MX6Q_GPC_V_OFFSET] | 
 | 	str	r6, [r11, #MX6Q_GPC_IMR1] | 
 | 	str	r7, [r11, #MX6Q_GPC_IMR2] | 
 | 	str	r8, [r11, #MX6Q_GPC_IMR3] | 
 | 	str	r9, [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	r6, =2000 | 
 | rbc_loop: | 
 | 	subs	r6, r6, #0x1 | 
 | 	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 MMDC IO first | 
 | 	 */ | 
 | 	mov	r5, #0x0 | 
 | 	resume_mmdc | 
 |  | 
 | 	/* return to suspend finish */ | 
 | 	ret	lr | 
 |  | 
 | resume: | 
 | 	/* invalidate L1 I-cache first */ | 
 | 	mov     r6, #0x0 | 
 | 	mcr     p15, 0, r6, c7, c5, 0 | 
 | 	mcr     p15, 0, r6, c7, c5, 6 | 
 | 	/* enable the Icache and branch prediction */ | 
 | 	mov     r6, #0x1800 | 
 | 	mcr     p15, 0, r6, c1, c0, 0 | 
 | 	isb | 
 |  | 
 | 	/* get physical resume address from pm_info. */ | 
 | 	ldr	lr, [r0, #PM_INFO_RESUME_ADDR_OFFSET] | 
 | 	/* clear core0's entry and parameter */ | 
 | 	ldr	r11, [r0, #PM_INFO_MX6Q_SRC_P_OFFSET] | 
 | 	mov	r7, #0x0 | 
 | 	str	r7, [r11, #MX6Q_SRC_GPR1] | 
 | 	str	r7, [r11, #MX6Q_SRC_GPR2] | 
 |  | 
 | 	ldr	r3, [r0, #PM_INFO_DDR_TYPE_OFFSET] | 
 | 	mov	r5, #0x1 | 
 | 	resume_mmdc | 
 |  | 
 | 	ret	lr | 
 | ENDPROC(imx6_suspend) | 
 |  | 
 | /* | 
 |  * The following code must assume it is running from physical address | 
 |  * where absolute virtual addresses to the data section have to be | 
 |  * turned into relative ones. | 
 |  */ | 
 |  | 
 | ENTRY(v7_cpu_resume) | 
 | 	bl	v7_invalidate_l1 | 
 | #ifdef CONFIG_CACHE_L2X0 | 
 | 	bl	l2c310_early_resume | 
 | #endif | 
 | 	b	cpu_resume | 
 | ENDPROC(v7_cpu_resume) |