/* * 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_PBASE_OFFSET 0x0 #define PM_INFO_RESUME_ADDR_OFFSET 0x4 #define PM_INFO_PM_INFO_SIZE_OFFSET 0x8 #define PM_INFO_PM_INFO_TTBR_OFFSET 0xc #define PM_INFO_MX6Q_MMDC_P_OFFSET 0x10 #define PM_INFO_MX6Q_MMDC_V_OFFSET 0x14 #define PM_INFO_MX6Q_IOMUXC_P_OFFSET 0x18 #define PM_INFO_MX6Q_IOMUXC_V_OFFSET 0x1c #define PM_INFO_MX6Q_CCM_P_OFFSET 0x20 #define PM_INFO_MX6Q_CCM_V_OFFSET 0x24 #define PM_INFO_MX6Q_GPC_P_OFFSET 0x28 #define PM_INFO_MX6Q_GPC_V_OFFSET 0x2c #define PM_INFO_MX6Q_ANATOP_P_OFFSET 0x30 #define PM_INFO_MX6Q_ANATOP_V_OFFSET 0x34 #define PM_INFO_MX6Q_SRC_P_OFFSET 0x38 #define PM_INFO_MX6Q_SRC_V_OFFSET 0x3c #define PM_INFO_MMDC_IO_NUM_OFFSET 0x40 #define PM_INFO_MMDC_IO_VAL_OFFSET 0x44 #define MX6Q_MMDC_MAPSR 0x404 #define MX6Q_MMDC_MPDGCTRL0 0x83c #define MX6Q_SRC_GPR1 0x20 #define MX6Q_SRC_GPR2 0x24 #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 .globl mx6ul_lpm_wfi_start .globl mx6ul_lpm_wfi_end .macro pll_do_wait_lock 1: ldr r7, [r10, r8] ands r7, #0x80000000 beq 1b .endm .macro ccm_do_wait 2: ldr r7, [r10, #0x48] cmp r7, #0x0 bne 2b .endm .macro ccm_enter_idle ldr r10, [r0, #PM_INFO_MX6Q_CCM_V_OFFSET] /* set ahb to 3MHz */ ldr r7, [r10, #0x14] orr r7, r7, #0x1c00 str r7, [r10, #0x14] /* set perclk to 6MHz */ ldr r7, [r10, #0x1c] bic r7, r7, #0x3f orr r7, r7, #0x3 str r7, [r10, #0x1c] /* set mmdc to 1MHz, periph2_clk2 need to be @8MHz */ ldr r7, [r10, #0x14] orr r7, r7, #0x2 orr r7, r7, #(0x7 << 3) str r7, [r10, #0x14] ccm_do_wait ldr r10, [r0, #PM_INFO_MX6Q_ANATOP_V_OFFSET] /* bypass PLL1 output to OSC */ ldr r7, [r10] orr r7, r7, #(0x1 << 16) str r7, [r10] ldr r10, [r0, #PM_INFO_MX6Q_CCM_V_OFFSET] /* set pll1_sw to from pll1 main */ ldr r7, [r10, #0xc] bic r7, r7, #0x4 str r7, [r10, #0xc] /* set step from osc */ ldr r7, [r10, #0xc] bic r7, r7, #0x100 str r7, [r10, #0xc] /* set pll1_sw to from step */ ldr r7, [r10, #0xc] orr r7, r7, #0x4 str r7, [r10, #0xc] ldr r10, [r0, #PM_INFO_MX6Q_ANATOP_V_OFFSET] /* Disable PLL1 bypass output */ ldr r7, [r10] bic r7, r7, #0x12000 str r7, [r10] /* * disable pll2, suppose when system enter low * power idle mode, only 396MHz pfd needs pll2, * now we switch arm clock to OSC, we can disable * pll2 now, gate pll2_pfd2 first. */ ldr r7, [r10, #0x100] orr r7, #0x800000 str r7, [r10, #0x100] ldr r7, [r10, #0x30] orr r7, r7, #0x1000 bic r7, r7, #0x2000 str r7, [r10, #0x30] .endm .macro ccm_exit_idle cmp r5, #0x0 ldreq r10, [r0, #PM_INFO_MX6Q_ANATOP_V_OFFSET] ldrne r10, [r0, #PM_INFO_MX6Q_ANATOP_P_OFFSET] /* enable pll2 and pll2_pfd2 */ ldr r7, [r10, #0x30] bic r7, r7, #0x1000 orr r7, r7, #0x2000 str r7, [r10, #0x30] ldr r8, =0x30 pll_do_wait_lock ldr r7, [r10, #0x100] bic r7, #0x800000 str r7, [r10, #0x100] /* enable PLL1 bypass output */ ldr r7, [r10] orr r7, r7, #0x12000 str r7, [r10] cmp r5, #0x0 ldreq r10, [r0, #PM_INFO_MX6Q_CCM_V_OFFSET] ldrne r10, [r0, #PM_INFO_MX6Q_CCM_P_OFFSET] /* set perclk back to 24MHz */ ldr r7, [r10, #0x1c] bic r7, r7, #0x3f str r7, [r10, #0x1c] /* set mmdc back to 24MHz */ ldr r7, [r10, #0x14] bic r7, r7, #0x7 bic r7, r7, #(0x7 << 3) str r7, [r10, #0x14] /* set ahb div back to 24MHz */ ldr r7, [r10, #0x14] bic r7, r7, #0x1c00 str r7, [r10, #0x14] ccm_do_wait /* set pll1_sw to from pll1 main */ ldr r7, [r10, #0xc] bic r7, r7, #0x4 str r7, [r10, #0xc] /* set step from pll2_pfd2 */ ldr r7, [r10, #0xc] orr r7, r7, #0x100 str r7, [r10, #0xc] /* set pll1_sw to from step */ ldr r7, [r10, #0xc] orr r7, r7, #0x4 str r7, [r10, #0xc] cmp r5, #0x0 ldreq r10, [r0, #PM_INFO_MX6Q_ANATOP_V_OFFSET] ldrne r10, [r0, #PM_INFO_MX6Q_ANATOP_P_OFFSET] /* Unbypass PLL1 */ ldr r7, [r10] bic r7, r7, #(0x1 << 16) str r7, [r10] .endm .macro anatop_enter_idle ldr r10, [r0, #PM_INFO_MX6Q_ANATOP_V_OFFSET] /* * check whether any PLL is enabled, as only when * there is no PLLs enabled, 2P5 and 1P1 can be * off and only enable weak ones. */ /* arm pll1 */ ldr r7, [r10, #0] ands r7, r7, #(1 << 31) bne 10f /* sys pll2 */ ldr r7, [r10, #0x30] ands r7, r7, #(1 << 31) bne 10f /* usb pll3 */ ldr r7, [r10, #0x10] ands r7, r7, #(1 << 31) bne 10f /* audio pll4 */ ldr r7, [r10, #0x70] ands r7, r7, #(1 << 31) bne 10f /* vidio pll5 */ ldr r7, [r10, #0xa0] ands r7, r7, #(1 << 31) bne 10f /* enet pll6 */ ldr r7, [r10, #0xe0] ands r7, r7, #(1 << 31) bne 10f /* usb host pll7 */ ldr r7, [r10, #0x20] ands r7, r7, #(1 << 31) bne 10f /* enable weak 2P5 and turn off regular 2P5 */ ldr r7, [r10, #0x130] orr r7, r7, #0x40000 str r7, [r10, #0x130] bic r7, r7, #0x1 str r7, [r10, #0x130] /* enable weak 1p1 and turn off regular 1P1 */ ldr r7, [r10, #0x110] orr r7, r7, #0x40000 str r7, [r10, #0x110] bic r7, r7, #0x1 str r7, [r10, #0x110] /* check whether ARM LDO is bypassed */ ldr r7, [r10, #0x140] and r7, r7, #0x1f cmp r7, #0x1f bne 10f /* low power band gap enable */ ldr r7, [r10, #0x270] orr r7, r7, #0x20 str r7, [r10, #0x270] /* turn off the bias current from the regular bandgap */ ldr r7, [r10, #0x270] orr r7, r7, #0x80 str r7, [r10, #0x270] /* * clear the REFTOP_SELFBIASOFF, * self-bias circuit of the band gap. * Per RM, should be cleared when * band gap is powered down. */ ldr r7, [r10, #0x150] bic r7, r7, #0x8 str r7, [r10, #0x150] /* turn off regular bandgap */ ldr r7, [r10, #0x150] orr r7, r7, #0x1 str r7, [r10, #0x150] /* switch to RC-OSC */ ldr r7, [r10, #0x270] orr r7, r7, #0x10 str r7, [r10, #0x270] /* turn off XTAL-OSC */ ldr r7, [r10, #0x150] orr r7, r7, #0x40000000 str r7, [r10, #0x150] 10: /* lower OSC current by 37.5% */ ldr r7, [r10, #0x150] orr r7, r7, #0x6000 str r7, [r10, #0x150] /* disconnect vdd_high_in and vdd_snvs_in */ ldr r7, [r10, #0x150] orr r7, r7, #0x1000 str r7, [r10, #0x150] .endm .macro anatop_exit_idle cmp r5, #0x0 ldreq r10, [r0, #PM_INFO_MX6Q_ANATOP_V_OFFSET] ldrne r10, [r0, #PM_INFO_MX6Q_ANATOP_P_OFFSET] /* increase OSC current to normal */ ldr r7, [r10, #0x150] bic r7, r7, #0x6000 str r7, [r10, #0x150] /* turn on XTAL-OSC and detector */ ldr r7, [r10, #0x150] bic r7, r7, #0x40000000 orr r7, r7, #0x10000 str r7, [r10, #0x150] /* wait for XTAL stable */ 14: ldr r7, [r10, #0x150] ands r7, r7, #0x8000 beq 14b /* switch to XTAL-OSC */ ldr r7, [r10, #0x270] bic r7, r7, #0x10 str r7, [r10, #0x270] /* turn off XTAL-OSC detector */ ldr r7, [r10, #0x150] bic r7, r7, #0x10000 str r7, [r10, #0x150] 15: /* check whether we need to enable 2P5/1P1 */ ldr r7, [r10, #0x110] ands r7, r7, #0x40000 beq 11f /* check whether ARM LDO is bypassed */ ldr r7, [r10, #0x140] and r7, r7, #0x1f cmp r7, #0x1f bne 12f /* turn on regular bandgap and wait for stable */ ldr r7, [r10, #0x150] bic r7, r7, #0x1 str r7, [r10, #0x150] 13: ldr r7, [r10, #0x150] ands r7, #0x80 beq 13b /* * set the REFTOP_SELFBIASOFF, * self-bias circuit of the band gap. */ ldr r7, [r10, #0x150] orr r7, r7, #0x8 str r7, [r10, #0x150] /* turn on the bias current from the regular bandgap */ ldr r7, [r10, #0x270] bic r7, r7, #0x80 str r7, [r10, #0x270] /* low power band gap disable */ ldr r7, [r10, #0x270] bic r7, r7, #0x20 str r7, [r10, #0x270] 12: /* enable regular 2P5 and turn off weak 2P5 */ ldr r7, [r10, #0x130] orr r7, r7, #0x1 str r7, [r10, #0x130] /* Ensure the 2P5 is up. */ 3: ldr r7, [r10, #0x130] ands r7, r7, #0x20000 beq 3b ldr r7, [r10, #0x130] bic r7, r7, #0x40000 str r7, [r10, #0x130] /* enable regular 1p1 and turn off weak 1P1 */ ldr r7, [r10, #0x110] orr r7, r7, #0x1 str r7, [r10, #0x110] 4: ldr r7, [r10, #0x110] ands r7, r7, #0x20000 beq 4b ldr r7, [r10, #0x110] bic r7, r7, #0x40000 str r7, [r10, #0x110] 11: .endm .macro disable_l1_dcache /* * Flush all data from the L1 data cache before disabling * SCTLR.C bit. */ push {r0 - r10, lr} ldr r7, =v7_flush_dcache_all mov lr, pc mov pc, r7 pop {r0 - r10, 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 - r10, lr} ldr r7, =v7_flush_dcache_all mov lr, pc mov pc, r7 pop {r0 - r10, lr} .endm .macro mmdc_enter_dvfs_mode /* disable automatic power savings. */ ldr r7, [r10, #MX6Q_MMDC_MAPSR] orr r7, r7, #0x1 str r7, [r10, #MX6Q_MMDC_MAPSR] /* disable power down timer */ ldr r7, [r10, #0x4] bic r7, r7, #0xff00 str r7, [r10, #0x4] /* make the DDR explicitly enter self-refresh. */ ldr r7, [r10, #MX6Q_MMDC_MAPSR] orr r7, r7, #(1 << 21) str r7, [r10, #MX6Q_MMDC_MAPSR] 5: ldr r7, [r10, #MX6Q_MMDC_MAPSR] ands r7, r7, #(1 << 25) beq 5b .endm .macro resume_mmdc /* restore MMDC IO */ cmp r5, #0x0 ldreq r10, [r0, #PM_INFO_MX6Q_IOMUXC_V_OFFSET] ldrne r10, [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 6: ldr r8, [r7], #0x4 ldr r9, [r7], #0x4 str r9, [r10, r8] subs r6, r6, #0x1 bne 6b cmp r5, #0x0 ldreq r10, [r0, #PM_INFO_MX6Q_MMDC_V_OFFSET] ldrne r10, [r0, #PM_INFO_MX6Q_MMDC_P_OFFSET] /* reset read FIFO, RST_RD_FIFO */ ldr r7, =MX6Q_MMDC_MPDGCTRL0 ldr r6, [r10, r7] orr r6, r6, #(1 << 31) str r6, [r10, r7] 7: ldr r6, [r10, r7] ands r6, r6, #(1 << 31) bne 7b /* reset FIFO a second time */ ldr r6, [r10, r7] orr r6, r6, #(1 << 31) str r6, [r10, r7] 8: ldr r6, [r10, r7] ands r6, r6, #(1 << 31) bne 8b /* let DDR out of self-refresh */ ldr r7, [r10, #MX6Q_MMDC_MAPSR] bic r7, r7, #(1 << 21) str r7, [r10, #MX6Q_MMDC_MAPSR] 9: ldr r7, [r10, #MX6Q_MMDC_MAPSR] ands r7, r7, #(1 << 25) bne 9b /* enable power down timer */ ldr r7, [r10, #0x4] orr r7, r7, #0x5500 str r7, [r10, #0x4] /* enable DDR auto power saving */ ldr r7, [r10, #MX6Q_MMDC_MAPSR] bic r7, r7, #0x1 str r7, [r10, #MX6Q_MMDC_MAPSR] .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. */ ldr r6, =iram_tlb_phys_addr ldr r7, [r6] /* Flush the BTAC. */ ldr r6, =0x0 mcr p15, 0, r6, c7, c1, 6 /* 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 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 /* Restore the TTBCR */ dsb isb /* 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 dsb isb /* 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 .extern iram_tlb_phys_addr /* imx6ul_low_power_idle */ .align 3 ENTRY(imx6ul_low_power_idle) mx6ul_lpm_wfi_start: push {r4 - r10} /* 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, =imx6ul_low_power_idle ldr r6, =wakeup sub r6, r6, r5 add r8, r1, r2 add r3, r8, r6 /* store physical resume addr and pm_info address. */ ldr r10, [r0, #PM_INFO_MX6Q_SRC_V_OFFSET] str r3, [r10, #0x20] str r1, [r10, #0x24] /* set ARM power to be gated */ ldr r10, [r0, #PM_INFO_MX6Q_GPC_V_OFFSET] ldr r7, =0x1 str r7, [r10, #0x2a0] disable_l1_dcache tlb_set_to_ocram /* make sure MMDC in self-refresh */ ldr r10, [r0, #PM_INFO_MX6Q_MMDC_V_OFFSET] mmdc_enter_dvfs_mode /* save DDR IO settings */ ldr r10, [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 save_and_set_mmdc_io_lpm: ldr r9, [r8], #0x4 ldr r5, [r10, r9] str r6, [r10, r9] str r5, [r8], #0x4 subs r7, r7, #0x1 bne save_and_set_mmdc_io_lpm mov r5, #0x0 ccm_enter_idle anatop_enter_idle /* * mask all GPC interrupts before * enabling the RBC counters to * avoid the counter starting too * early if an interupt is already * pending. */ ldr r10, [r0, #PM_INFO_MX6Q_GPC_V_OFFSET] ldr r4, [r10, #MX6Q_GPC_IMR1] ldr r5, [r10, #MX6Q_GPC_IMR2] ldr r6, [r10, #MX6Q_GPC_IMR3] ldr r7, [r10, #MX6Q_GPC_IMR4] ldr r3, =0xffffffff str r3, [r10, #MX6Q_GPC_IMR1] str r3, [r10, #MX6Q_GPC_IMR2] str r3, [r10, #MX6Q_GPC_IMR3] str r3, [r10, #MX6Q_GPC_IMR4] /* * enable the RBC bypass counter here * to hold off the interrupts. RBC counter * = 4 (120us). With this setting, the latency * from wakeup interrupt to ARM power up * is ~130uS. */ ldr r10, [r0, #PM_INFO_MX6Q_CCM_V_OFFSET] ldr r3, [r10, #MX6Q_CCM_CCR] bic r3, r3, #(0x3f << 21) orr r3, r3, #(0x4 << 21) str r3, [r10, #MX6Q_CCM_CCR] /* enable the counter. */ ldr r3, [r10, #MX6Q_CCM_CCR] orr r3, r3, #(0x1 << 27) str r3, [r10, #MX6Q_CCM_CCR] /* unmask all the GPC interrupts. */ ldr r10, [r0, #PM_INFO_MX6Q_GPC_V_OFFSET] str r4, [r10, #MX6Q_GPC_IMR1] str r5, [r10, #MX6Q_GPC_IMR2] str r6, [r10, #MX6Q_GPC_IMR3] str r7, [r10, #MX6Q_GPC_IMR4] /* * now delay for a short while (3usec) * 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, =50 rbc_loop: subs r4, r4, #0x1 bne rbc_loop 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 mov r5, #0x0 anatop_exit_idle ccm_exit_idle /* clear ARM power gate setting */ ldr r10, [r0, #PM_INFO_MX6Q_GPC_V_OFFSET] ldr r7, =0x0 str r7, [r10, #0x2a0] resume_mmdc /* enable d-cache */ mrc p15, 0, r7, c1, c0, 0 orr r7, r7, #(1 << 2) mcr p15, 0, r7, c1, c0, 0 tlb_back_to_ddr /* Restore registers */ pop {r4 - r10} 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 /* get physical resume address from pm_info. */ ldr lr, [r0, #PM_INFO_RESUME_ADDR_OFFSET] /* clear core0's entry and parameter */ ldr r10, [r0, #PM_INFO_MX6Q_SRC_P_OFFSET] mov r7, #0x0 str r7, [r10, #MX6Q_SRC_GPR1] str r7, [r10, #MX6Q_SRC_GPR2] /* clear ARM power gate setting */ ldr r10, [r0, #PM_INFO_MX6Q_GPC_P_OFFSET] ldr r7, =0x0 str r7, [r10, #0x2a0] mov r5, #0x1 anatop_exit_idle ccm_exit_idle resume_mmdc /* Restore registers */ mov pc, lr .ltorg mx6ul_lpm_wfi_end: