/* * Copyright (C) 2015 Freescale Semiconductor, Inc. All Rights Reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ #include #define PM_INFO_VBASE_OFFSET 0x0 #define PM_INFO_PBASE_OFFSET 0x4 #define PM_INFO_RESUME_ADDR_OFFSET 0x8 #define PM_INFO_PM_INFO_SIZE_OFFSET 0xc #define PM_INFO_PM_INFO_TTBR_OFFSET 0x10 #define PM_INFO_PM_INFO_NUM_ONLINE_CPUS_OFFSET 0x14 #define PM_INFO_PM_INFO_NUM_LPI_CPUS_OFFSET 0x18 #define PM_INFO_VAL_OFFSET 0x1c #define PM_INFO_FLAG0_OFFSET 0x20 #define PM_INFO_FLAG1_OFFSET 0x24 #define PM_INFO_MX7D_DDRC_P_OFFSET 0x28 #define PM_INFO_MX7D_DDRC_V_OFFSET 0x2c #define PM_INFO_MX7D_CCM_P_OFFSET 0x30 #define PM_INFO_MX7D_CCM_V_OFFSET 0x34 #define PM_INFO_MX7D_ANATOP_P_OFFSET 0x38 #define PM_INFO_MX7D_ANATOP_V_OFFSET 0x3c #define PM_INFO_MX7D_SRC_P_OFFSET 0x40 #define PM_INFO_MX7D_SRC_V_OFFSET 0x44 #define PM_INFO_MX7D_IOMUXC_GPR_P_OFFSET 0x48 #define PM_INFO_MX7D_IOMUXC_GPR_V_OFFSET 0x4c #define PM_INFO_MX7D_GPC_P_OFFSET 0x50 #define PM_INFO_MX7D_GPC_V_OFFSET 0x54 #define PM_INFO_MX7D_GIC_DIST_P_OFFSET 0x58 #define PM_INFO_MX7D_GIC_DIST_V_OFFSET 0x5c #define MX7D_SRC_GPR1 0x74 #define MX7D_SRC_GPR2 0x78 #define MX7D_SRC_GPR3 0x7c #define MX7D_SRC_GPR4 0x80 #define MX7D_GPC_IMR1 0x30 #define MX7D_GPC_IMR2 0x34 #define MX7D_GPC_IMR3 0x38 #define MX7D_GPC_IMR4 0x3c #define DDRC_STAT 0x4 #define DDRC_PWRCTL 0x30 #define DDRC_DBG1 0x304 #define DDRC_DBGCAM 0x308 #define DDRC_PSTAT 0x3fc #define DDRC_PCTRL_0 0x490 /* * imx_pen_lock * * The reference link of Peterson's algorithm: * http://en.wikipedia.org/wiki/Peterson's_algorithm * * val1 = r1 = !turn (inverted from Peterson's algorithm) * on cpu 0: * r2 = flag[0] (in flag0) * r3 = flag[1] (in flag1) * on cpu1: * r2 = flag[1] (in flag1) * r3 = flag[0] (in flag0) * */ .macro imx_pen_lock mov r8, r0 mrc p15, 0, r5, c0, c0, 5 and r5, r5, #3 add r6, r8, #PM_INFO_VAL_OFFSET cmp r5, #0 addeq r7, r8, #PM_INFO_FLAG0_OFFSET addeq r8, r8, #PM_INFO_FLAG1_OFFSET addne r7, r8, #PM_INFO_FLAG1_OFFSET addne r8, r8, #PM_INFO_FLAG0_OFFSET mov r9, #1 str r9, [r7] dsb str r5, [r6] 1: dsb ldr r9, [r8] cmp r9, #1 ldreq r9, [r6] cmpeq r9, r5 beq 1b .endm .macro imx_pen_unlock dsb mrc p15, 0, r6, c0, c0, 5 and r6, r6, #3 cmp r6, #0 addeq r7, r0, #PM_INFO_FLAG0_OFFSET addne r7, r0, #PM_INFO_FLAG1_OFFSET mov r9, #0 str r9, [r7] .endm .macro disable_l1_dcache push {r0 - r12, lr} ldr r7, =v7_flush_dcache_all mov lr, pc mov pc, r7 pop {r0 - r12, lr} /* disable d-cache */ mrc p15, 0, r7, c1, c0, 0 bic r7, r7, #(1 << 2) mcr p15, 0, r7, c1, c0, 0 dsb isb push {r0 - r12, lr} ldr r7, =v7_flush_dcache_all mov lr, pc mov pc, r7 pop {r0 - r12, lr} #ifdef CONFIG_SMP clrex /* Turn off SMP bit. */ mrc p15, 0, r8, c1, c0, 1 bic r8, r8, #0x40 mcr p15, 0, r8, c1, c0, 1 isb dsb #endif .endm .macro tlb_set_to_ocram /* save ttbr */ mrc p15, 0, r7, c2, c0, 1 str r7, [r0, #PM_INFO_PM_INFO_TTBR_OFFSET] /* * To ensure no page table walks occur in DDR, we * have a another page table stored in IRAM that only * contains entries pointing to IRAM, AIPS1 and AIPS2. * We need to set the TTBR1 to the new IRAM TLB. * Do the following steps: * 1. Flush the Branch Target Address Cache (BTAC) * 2. Set TTBR1 to point to IRAM page table. * 3. Disable page table walks in TTBR0 (PD0 = 1) * 4. Set TTBR0.N=1, implying 0-2G is translated by TTBR0 * and 2-4G is translated by TTBR1. */ /* Disable Branch Prediction, Z bit in SCTLR. */ mrc p15, 0, r6, c1, c0, 0 bic r6, r6, #0x800 mcr p15, 0, r6, c1, c0, 0 /* Flush the BTAC. */ ldr r6, =0x0 mcr p15, 0, r6, c7, c1, 6 ldr r6, =iram_tlb_phys_addr ldr r7, [r6] dsb isb /* Store the IRAM table in TTBR1 */ mcr p15, 0, r7, c2, c0, 1 /* Read TTBCR and set PD0=1, N = 1 */ mrc p15, 0, r6, c2, c0, 2 orr r6, r6, #0x11 mcr p15, 0, r6, c2, c0, 2 dsb isb /* flush the TLB */ ldr r6, =0x0 mcr p15, 0, r6, c8, c3, 0 .endm .macro tlb_back_to_ddr /* Read TTBCR and set PD0=0, N = 0 */ mrc p15, 0, r6, c2, c0, 2 bic r6, r6, #0x11 mcr p15, 0, r6, c2, c0, 2 dsb isb /* flush the TLB */ ldr r6, =0x0 mcr p15, 0, r6, c8, c3, 0 /* Enable Branch Prediction, Z bit in SCTLR. */ mrc p15, 0, r6, c1, c0, 0 orr r6, r6, #0x800 mcr p15, 0, r6, c1, c0, 0 /* Flush the Branch Target Address Cache (BTAC) */ ldr r6, =0x0 mcr p15, 0, r6, c7, c1, 6 /* restore ttbr */ ldr r7, [r0, #PM_INFO_PM_INFO_TTBR_OFFSET] mcr p15, 0, r7, c2, c0, 1 .endm /* r10 must be DDRC base address */ .macro ddrc_enter_self_refresh ldr r10, [r0, #PM_INFO_MX7D_DDRC_V_OFFSET] /* disable port */ ldr r7, =0x0 str r7, [r10, #DDRC_PCTRL_0] /* let DDR out of self-refresh */ ldr r7, =0x0 str r7, [r10, #DDRC_PWRCTL] /* wait rw port_busy clear */ ldr r6, =(0x1 << 16) orr r6, r6, #0x1 2: ldr r7, [r10, #DDRC_PSTAT] ands r7, r7, r6 bne 2b ldr r7, =0x1 str r7, [r10, #DDRC_DBG1] ldr r6, =0x36000000 11: ldr r7, [r10, #DDRC_DBGCAM] and r7, r7, r6 cmp r7, r6 bne 11b /* enter self-refresh bit 5 */ ldr r7, =(0x1 << 5) str r7, [r10, #DDRC_PWRCTL] /* wait until self-refresh mode entered */ 3: ldr r7, [r10, #DDRC_STAT] and r7, r7, #0x3 cmp r7, #0x3 bne 3b 4: ldr r7, [r10, #DDRC_STAT] ands r7, r7, #0x20 beq 4b /* disable dram clk */ ldr r7, [r10, #DDRC_PWRCTL] orr r7, r7, #(1 << 3) str r7, [r10, #DDRC_PWRCTL] /* * TO1.1 adds feature of DDR pads power down, * although TO1.0 has no such function, but it is * NOT harmful to program GPR registers for TO1.0, * it can avoid the logic of version check in idle * thread. */ ldr r10, [r0, #PM_INFO_MX7D_IOMUXC_GPR_V_OFFSET] ldr r7, =0xf0000 str r7, [r10] /* delay 20us, measured by gpio */ ldr r7, =20 12: subs r7, r7, #0x1 bne 12b .endm /* r10 must be DDRC base address */ .macro ddrc_exit_self_refresh cmp r5, #0x1 ldreq r10, [r0, #PM_INFO_MX7D_IOMUXC_GPR_P_OFFSET] ldrne r10, [r0, #PM_INFO_MX7D_IOMUXC_GPR_V_OFFSET] ldr r7, =0x0 str r7, [r10] ldr r7, =20 13: subs r7, r7, #0x1 bne 13b cmp r5, #0x1 ldreq r10, [r0, #PM_INFO_MX7D_DDRC_P_OFFSET] ldrne r10, [r0, #PM_INFO_MX7D_DDRC_V_OFFSET] ldr r7, =0x0 str r7, [r10, #DDRC_DBG1] ldr r6, =0x30000000 14: ldr r7, [r10, #DDRC_DBGCAM] and r7, r7, r6 cmp r7, r6 bne 14b /* let DDR out of self-refresh */ ldr r7, =0x0 str r7, [r10, #DDRC_PWRCTL] /* wait until self-refresh mode exited */ 5: ldr r7, [r10, #DDRC_STAT] and r7, r7, #0x3 cmp r7, #0x3 beq 5b /* enable auto self-refresh */ ldr r7, [r10, #DDRC_PWRCTL] orr r7, r7, #(1 << 0) str r7, [r10, #DDRC_PWRCTL] ldr r7, =0x1 str r7, [r10, #DDRC_PCTRL_0] .endm .macro pll_do_wait_lock 6: ldr r7, [r10, r8] ands r7, #0x80000000 beq 6b .endm .macro ccm_enter_idle ldr r10, [r0, #PM_INFO_MX7D_ANATOP_V_OFFSET] /* ungate pfd1 332m for lower axi */ ldr r7, =0x8000 str r7, [r10, #0xc8] ldr r10, [r0, #PM_INFO_MX7D_CCM_V_OFFSET] /* switch ARM CLK to OSC */ ldr r8, =0x8000 ldr r7, [r10, r8] bic r7, r7, #0x7000000 str r7, [r10, r8] /* lower AXI clk from 24MHz to 3MHz */ ldr r8, =0x8800 ldr r7, [r10, r8] orr r7, r7, #0x7 str r7, [r10, r8] /* lower AHB clk from 24MHz to 3MHz */ ldr r8, =0x9000 ldr r7, [r10, r8] orr r7, r7, #0x7 str r7, [r10, r8] /* gate dram clk */ ldr r8, =0x9880 ldr r7, [r10, r8] bic r7, r7, #0x10000000 str r7, [r10, r8] ldr r10, [r0, #PM_INFO_MX7D_ANATOP_V_OFFSET] /* gate pfd1 332m */ ldr r7, =0x8000 str r7, [r10, #0xc4] /* gate system pll pfd div 1 */ ldr r7, =0x10 str r7, [r10, #0xb4] /* power down ARM, 480 and DRAM PLL */ ldr r7, =0x1000 str r7, [r10, #0x64] str r7, [r10, #0xb4] ldr r7, =0x100000 str r7, [r10, #0x74] .endm .macro ccm_exit_idle cmp r5, #0x1 ldreq r10, [r0, #PM_INFO_MX7D_ANATOP_P_OFFSET] ldrne r10, [r0, #PM_INFO_MX7D_ANATOP_V_OFFSET] /* power up ARM, 480 and DRAM PLL */ ldr r7, =0x1000 str r7, [r10, #0x68] ldr r8, =0x60 pll_do_wait_lock ldr r7, =0x1000 str r7, [r10, #0xb8] ldr r8, =0xb0 pll_do_wait_lock ldr r7, =0x100000 str r7, [r10, #0x78] ldr r8, =0x70 pll_do_wait_lock /* ungate pfd1 332m for lower axi */ ldr r7, =0x8000 str r7, [r10, #0xc8] /* ungate system pll pfd div 1 */ ldr r7, =0x10 str r7, [r10, #0xb8] cmp r5, #0x1 ldreq r10, [r0, #PM_INFO_MX7D_CCM_P_OFFSET] ldrne r10, [r0, #PM_INFO_MX7D_CCM_V_OFFSET] /* switch ARM CLK to PLL */ ldr r8, =0x8000 ldr r7, [r10, r8] orr r7, r7, #0x1000000 str r7, [r10, r8] /* restore AXI clk from 3MHz to 24MHz */ ldr r8, =0x8800 ldr r7, [r10, r8] bic r7, r7, #0x7 str r7, [r10, r8] /* restore AHB clk from 3MHz to 24MHz */ ldr r8, =0x9000 ldr r7, [r10, r8] bic r7, r7, #0x7 str r7, [r10, r8] /* ungate dram clk */ ldr r8, =0x9880 ldr r7, [r10, r8] orr r7, r7, #0x10000000 str r7, [r10, r8] cmp r5, #0x1 ldreq r10, [r0, #PM_INFO_MX7D_ANATOP_P_OFFSET] ldrne r10, [r0, #PM_INFO_MX7D_ANATOP_V_OFFSET] /* gate pfd1 332m for lower axi */ ldr r7, =0x8000 str r7, [r10, #0xc4] .endm .macro anatop_enter_idle ldr r10, [r0, #PM_INFO_MX7D_ANATOP_V_OFFSET] /* XTAL to RC-OSC switch */ ldr r7, [r10] orr r7, r7, #0x1000 str r7, [r10] /* power down XTAL */ ldr r7, [r10] orr r7, r7, #0x1 str r7, [r10] /* enable weak 1P0A */ ldr r7, [r10, #0x200] orr r7, r7, #0x40000 str r7, [r10, #0x200] /* disable LDO 1P0A */ ldr r7, [r10, #0x200] bic r7, r7, #0x1 str r7, [r10, #0x200] /* disable LDO 1P0D */ ldr r7, [r10, #0x210] bic r7, r7, #0x1 str r7, [r10, #0x210] /* disable LDO 1P2 */ ldr r7, [r10, #0x220] bic r7, r7, #0x1 str r7, [r10, #0x220] /* switch to low power bandgap */ ldr r7, [r10, #0x270] orr r7, r7, #0x400 str r7, [r10, #0x270] /* power down normal bandgap */ orr r7, r7, #0x1 str r7, [r10, #0x270] .endm .macro anatop_exit_idle cmp r5, #0x1 ldreq r10, [r0, #PM_INFO_MX7D_ANATOP_P_OFFSET] ldrne r10, [r0, #PM_INFO_MX7D_ANATOP_V_OFFSET] /* power on normal bandgap */ ldr r7, [r10, #0x270] bic r7, r7, #0x1 str r7, [r10, #0x270] /* switch to normal bandgap */ bic r7, r7, #0x400 str r7, [r10, #0x270] /* enable LDO 1P2 */ ldr r7, [r10, #0x220] orr r7, r7, #0x1 str r7, [r10, #0x220] 7: ldr r7, [r10, #0x220] ands r7, #0x20000 beq 7b /* enable LDO 1P0D */ ldr r7, [r10, #0x210] orr r7, r7, #0x1 str r7, [r10, #0x210] 8: ldr r7, [r10, #0x210] ands r7, #0x20000 beq 8b /* enable LDO 1P0A */ ldr r7, [r10, #0x200] orr r7, r7, #0x1 str r7, [r10, #0x200] 9: ldr r7, [r10, #0x200] ands r7, #0x20000 beq 9b /* disable weak 1P0A */ ldr r7, [r10, #0x200] bic r7, r7, #0x40000 str r7, [r10, #0x200] /* power up XTAL and wait */ ldr r7, [r10] bic r7, r7, #0x1 str r7, [r10] 10: ldr r7, [r10] ands r7, r7, #0x4 beq 10b /* RC-OSC to XTAL switch */ ldr r7, [r10] bic r7, r7, #0x1000 str r7, [r10] .endm .extern iram_tlb_phys_addr .align 3 ENTRY(imx7d_low_power_idle) push {r0 - r12} /* get necessary info from pm_info */ ldr r1, [r0, #PM_INFO_PBASE_OFFSET] ldr r2, [r0, #PM_INFO_PM_INFO_SIZE_OFFSET] /* * counting the resume address in iram * to set it in SRC register. */ ldr r5, =imx7d_low_power_idle ldr r6, =wakeup sub r6, r6, r5 add r8, r1, r2 add r3, r8, r6 /* r11 is cpu id */ mrc p15, 0, r11, c0, c0, 5 and r11, r11, #3 cmp r11, #0x0 ldreq r6, =MX7D_SRC_GPR1 ldreq r7, =MX7D_SRC_GPR2 ldrne r6, =MX7D_SRC_GPR3 ldrne r7, =MX7D_SRC_GPR4 /* store physical resume addr and pm_info address. */ ldr r10, [r0, #PM_INFO_MX7D_SRC_V_OFFSET] str r3, [r10, r6] str r1, [r10, r7] disable_l1_dcache tlb_set_to_ocram /* check last to sleep */ ldr r6, [r0, #PM_INFO_PM_INFO_NUM_ONLINE_CPUS_OFFSET] ldr r7, [r0, #PM_INFO_PM_INFO_NUM_LPI_CPUS_OFFSET] cmp r6, r7 bne lpi_enter_done ddrc_enter_self_refresh ccm_enter_idle anatop_enter_idle ldr r10, [r0, #PM_INFO_MX7D_GIC_DIST_V_OFFSET] ldr r7, =0x0 ldr r8, =0x1000 str r7, [r10, r8] ldr r10, [r0, #PM_INFO_MX7D_GPC_V_OFFSET] ldr r4, [r10, #MX7D_GPC_IMR1] ldr r5, [r10, #MX7D_GPC_IMR2] ldr r6, [r10, #MX7D_GPC_IMR3] ldr r7, [r10, #MX7D_GPC_IMR4] ldr r8, =0xffffffff str r8, [r10, #MX7D_GPC_IMR1] str r8, [r10, #MX7D_GPC_IMR2] str r8, [r10, #MX7D_GPC_IMR3] str r8, [r10, #MX7D_GPC_IMR4] /* * enable the RBC bypass counter here * to hold off the interrupts. RBC counter * = 8 (240us). With this setting, the latency * from wakeup interrupt to ARM power up * is ~250uS. */ ldr r8, [r10, #0x14] bic r8, r8, #(0x3f << 24) orr r8, r8, #(0x8 << 24) str r8, [r10, #0x14] /* enable the counter. */ ldr r8, [r10, #0x14] orr r8, r8, #(0x1 << 30) str r8, [r10, #0x14] /* unmask all the GPC interrupts. */ str r4, [r10, #MX7D_GPC_IMR1] str r5, [r10, #MX7D_GPC_IMR2] str r6, [r10, #MX7D_GPC_IMR3] str r7, [r10, #MX7D_GPC_IMR4] /* * now delay for a short while (30usec) * ARM is at 24MHz 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 r4, =5 rbc_loop: subs r4, r4, #0x1 bne rbc_loop lpi_enter_done: imx_pen_unlock wfi nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop imx_pen_lock /* check first to wake */ ldr r6, [r0, #PM_INFO_PM_INFO_NUM_ONLINE_CPUS_OFFSET] ldr r7, [r0, #PM_INFO_PM_INFO_NUM_LPI_CPUS_OFFSET] cmp r6, r7 bne skip_lpi_flow ldr r5, =0x0 anatop_exit_idle ccm_exit_idle ddrc_exit_self_refresh ldr r10, [r0, #PM_INFO_MX7D_GIC_DIST_V_OFFSET] ldr r7, =0x1 ldr r8, =0x1000 str r7, [r10, r8] skip_lpi_flow: tlb_back_to_ddr #ifdef CONFIG_SMP /* Turn on SMP bit. */ mrc p15, 0, r7, c1, c0, 1 orr r7, r7, #0x40 mcr p15, 0, r7, c1, c0, 1 isb #endif /* enable d-cache */ mrc p15, 0, r7, c1, c0, 0 orr r7, r7, #(1 << 2) mcr p15, 0, r7, c1, c0, 0 dsb isb /* Restore registers */ pop {r0 - r12} mov pc, lr wakeup: /* invalidate L1 I-cache first */ mov r1, #0x0 mcr p15, 0, r1, c7, c5, 0 mcr p15, 0, r1, c7, c5, 0 mcr p15, 0, r1, c7, c5, 6 /* enable the Icache and branch prediction */ mov r1, #0x1800 mcr p15, 0, r1, c1, c0, 0 isb imx_pen_lock /* check first to wake */ ldr r6, [r0, #PM_INFO_PM_INFO_NUM_ONLINE_CPUS_OFFSET] ldr r7, [r0, #PM_INFO_PM_INFO_NUM_LPI_CPUS_OFFSET] cmp r6, r7 bne wakeup_skip_lpi_flow ldr r5, =0x1 anatop_exit_idle ccm_exit_idle ddrc_exit_self_refresh wakeup_skip_lpi_flow: /* get physical resume address from pm_info. */ ldr lr, [r0, #PM_INFO_RESUME_ADDR_OFFSET] /* Restore registers */ mov pc, lr .ltorg ENDPROC(imx7d_low_power_idle)