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