162306a36Sopenharmony_ci/* SPDX-License-Identifier: GPL-2.0-or-later */ 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Copyright 2014 Freescale Semiconductor, Inc. 462306a36Sopenharmony_ci */ 562306a36Sopenharmony_ci 662306a36Sopenharmony_ci#include <linux/linkage.h> 762306a36Sopenharmony_ci#include <asm/assembler.h> 862306a36Sopenharmony_ci#include <asm/asm-offsets.h> 962306a36Sopenharmony_ci#include <asm/hardware/cache-l2x0.h> 1062306a36Sopenharmony_ci#include "hardware.h" 1162306a36Sopenharmony_ci 1262306a36Sopenharmony_ci.arch armv7-a 1362306a36Sopenharmony_ci 1462306a36Sopenharmony_ci/* 1562306a36Sopenharmony_ci * ==================== low level suspend ==================== 1662306a36Sopenharmony_ci * 1762306a36Sopenharmony_ci * Better to follow below rules to use ARM registers: 1862306a36Sopenharmony_ci * r0: pm_info structure address; 1962306a36Sopenharmony_ci * r1 ~ r4: for saving pm_info members; 2062306a36Sopenharmony_ci * r5 ~ r10: free registers; 2162306a36Sopenharmony_ci * r11: io base address. 2262306a36Sopenharmony_ci * 2362306a36Sopenharmony_ci * suspend ocram space layout: 2462306a36Sopenharmony_ci * ======================== high address ====================== 2562306a36Sopenharmony_ci * . 2662306a36Sopenharmony_ci * . 2762306a36Sopenharmony_ci * . 2862306a36Sopenharmony_ci * ^ 2962306a36Sopenharmony_ci * ^ 3062306a36Sopenharmony_ci * ^ 3162306a36Sopenharmony_ci * imx6_suspend code 3262306a36Sopenharmony_ci * PM_INFO structure(imx6_cpu_pm_info) 3362306a36Sopenharmony_ci * ======================== low address ======================= 3462306a36Sopenharmony_ci */ 3562306a36Sopenharmony_ci 3662306a36Sopenharmony_ci/* 3762306a36Sopenharmony_ci * Below offsets are based on struct imx6_cpu_pm_info 3862306a36Sopenharmony_ci * which defined in arch/arm/mach-imx/pm-imx6q.c, this 3962306a36Sopenharmony_ci * structure contains necessary pm info for low level 4062306a36Sopenharmony_ci * suspend related code. 4162306a36Sopenharmony_ci */ 4262306a36Sopenharmony_ci#define PM_INFO_PBASE_OFFSET 0x0 4362306a36Sopenharmony_ci#define PM_INFO_RESUME_ADDR_OFFSET 0x4 4462306a36Sopenharmony_ci#define PM_INFO_DDR_TYPE_OFFSET 0x8 4562306a36Sopenharmony_ci#define PM_INFO_PM_INFO_SIZE_OFFSET 0xC 4662306a36Sopenharmony_ci#define PM_INFO_MX6Q_MMDC_P_OFFSET 0x10 4762306a36Sopenharmony_ci#define PM_INFO_MX6Q_MMDC_V_OFFSET 0x14 4862306a36Sopenharmony_ci#define PM_INFO_MX6Q_SRC_P_OFFSET 0x18 4962306a36Sopenharmony_ci#define PM_INFO_MX6Q_SRC_V_OFFSET 0x1C 5062306a36Sopenharmony_ci#define PM_INFO_MX6Q_IOMUXC_P_OFFSET 0x20 5162306a36Sopenharmony_ci#define PM_INFO_MX6Q_IOMUXC_V_OFFSET 0x24 5262306a36Sopenharmony_ci#define PM_INFO_MX6Q_CCM_P_OFFSET 0x28 5362306a36Sopenharmony_ci#define PM_INFO_MX6Q_CCM_V_OFFSET 0x2C 5462306a36Sopenharmony_ci#define PM_INFO_MX6Q_GPC_P_OFFSET 0x30 5562306a36Sopenharmony_ci#define PM_INFO_MX6Q_GPC_V_OFFSET 0x34 5662306a36Sopenharmony_ci#define PM_INFO_MX6Q_L2_P_OFFSET 0x38 5762306a36Sopenharmony_ci#define PM_INFO_MX6Q_L2_V_OFFSET 0x3C 5862306a36Sopenharmony_ci#define PM_INFO_MMDC_IO_NUM_OFFSET 0x40 5962306a36Sopenharmony_ci#define PM_INFO_MMDC_IO_VAL_OFFSET 0x44 6062306a36Sopenharmony_ci 6162306a36Sopenharmony_ci#define MX6Q_SRC_GPR1 0x20 6262306a36Sopenharmony_ci#define MX6Q_SRC_GPR2 0x24 6362306a36Sopenharmony_ci#define MX6Q_MMDC_MAPSR 0x404 6462306a36Sopenharmony_ci#define MX6Q_MMDC_MPDGCTRL0 0x83c 6562306a36Sopenharmony_ci#define MX6Q_GPC_IMR1 0x08 6662306a36Sopenharmony_ci#define MX6Q_GPC_IMR2 0x0c 6762306a36Sopenharmony_ci#define MX6Q_GPC_IMR3 0x10 6862306a36Sopenharmony_ci#define MX6Q_GPC_IMR4 0x14 6962306a36Sopenharmony_ci#define MX6Q_CCM_CCR 0x0 7062306a36Sopenharmony_ci 7162306a36Sopenharmony_ci .align 3 7262306a36Sopenharmony_ci .arm 7362306a36Sopenharmony_ci 7462306a36Sopenharmony_ci .macro sync_l2_cache 7562306a36Sopenharmony_ci 7662306a36Sopenharmony_ci /* sync L2 cache to drain L2's buffers to DRAM. */ 7762306a36Sopenharmony_ci#ifdef CONFIG_CACHE_L2X0 7862306a36Sopenharmony_ci ldr r11, [r0, #PM_INFO_MX6Q_L2_V_OFFSET] 7962306a36Sopenharmony_ci teq r11, #0 8062306a36Sopenharmony_ci beq 6f 8162306a36Sopenharmony_ci mov r6, #0x0 8262306a36Sopenharmony_ci str r6, [r11, #L2X0_CACHE_SYNC] 8362306a36Sopenharmony_ci1: 8462306a36Sopenharmony_ci ldr r6, [r11, #L2X0_CACHE_SYNC] 8562306a36Sopenharmony_ci ands r6, r6, #0x1 8662306a36Sopenharmony_ci bne 1b 8762306a36Sopenharmony_ci6: 8862306a36Sopenharmony_ci#endif 8962306a36Sopenharmony_ci 9062306a36Sopenharmony_ci .endm 9162306a36Sopenharmony_ci 9262306a36Sopenharmony_ci .macro resume_mmdc 9362306a36Sopenharmony_ci 9462306a36Sopenharmony_ci /* restore MMDC IO */ 9562306a36Sopenharmony_ci cmp r5, #0x0 9662306a36Sopenharmony_ci ldreq r11, [r0, #PM_INFO_MX6Q_IOMUXC_V_OFFSET] 9762306a36Sopenharmony_ci ldrne r11, [r0, #PM_INFO_MX6Q_IOMUXC_P_OFFSET] 9862306a36Sopenharmony_ci 9962306a36Sopenharmony_ci ldr r6, [r0, #PM_INFO_MMDC_IO_NUM_OFFSET] 10062306a36Sopenharmony_ci ldr r7, =PM_INFO_MMDC_IO_VAL_OFFSET 10162306a36Sopenharmony_ci add r7, r7, r0 10262306a36Sopenharmony_ci1: 10362306a36Sopenharmony_ci ldr r8, [r7], #0x4 10462306a36Sopenharmony_ci ldr r9, [r7], #0x4 10562306a36Sopenharmony_ci str r9, [r11, r8] 10662306a36Sopenharmony_ci subs r6, r6, #0x1 10762306a36Sopenharmony_ci bne 1b 10862306a36Sopenharmony_ci 10962306a36Sopenharmony_ci cmp r5, #0x0 11062306a36Sopenharmony_ci ldreq r11, [r0, #PM_INFO_MX6Q_MMDC_V_OFFSET] 11162306a36Sopenharmony_ci ldrne r11, [r0, #PM_INFO_MX6Q_MMDC_P_OFFSET] 11262306a36Sopenharmony_ci 11362306a36Sopenharmony_ci cmp r3, #IMX_DDR_TYPE_LPDDR2 11462306a36Sopenharmony_ci bne 4f 11562306a36Sopenharmony_ci 11662306a36Sopenharmony_ci /* reset read FIFO, RST_RD_FIFO */ 11762306a36Sopenharmony_ci ldr r7, =MX6Q_MMDC_MPDGCTRL0 11862306a36Sopenharmony_ci ldr r6, [r11, r7] 11962306a36Sopenharmony_ci orr r6, r6, #(1 << 31) 12062306a36Sopenharmony_ci str r6, [r11, r7] 12162306a36Sopenharmony_ci2: 12262306a36Sopenharmony_ci ldr r6, [r11, r7] 12362306a36Sopenharmony_ci ands r6, r6, #(1 << 31) 12462306a36Sopenharmony_ci bne 2b 12562306a36Sopenharmony_ci 12662306a36Sopenharmony_ci /* reset FIFO a second time */ 12762306a36Sopenharmony_ci ldr r6, [r11, r7] 12862306a36Sopenharmony_ci orr r6, r6, #(1 << 31) 12962306a36Sopenharmony_ci str r6, [r11, r7] 13062306a36Sopenharmony_ci3: 13162306a36Sopenharmony_ci ldr r6, [r11, r7] 13262306a36Sopenharmony_ci ands r6, r6, #(1 << 31) 13362306a36Sopenharmony_ci bne 3b 13462306a36Sopenharmony_ci4: 13562306a36Sopenharmony_ci /* let DDR out of self-refresh */ 13662306a36Sopenharmony_ci ldr r7, [r11, #MX6Q_MMDC_MAPSR] 13762306a36Sopenharmony_ci bic r7, r7, #(1 << 21) 13862306a36Sopenharmony_ci str r7, [r11, #MX6Q_MMDC_MAPSR] 13962306a36Sopenharmony_ci5: 14062306a36Sopenharmony_ci ldr r7, [r11, #MX6Q_MMDC_MAPSR] 14162306a36Sopenharmony_ci ands r7, r7, #(1 << 25) 14262306a36Sopenharmony_ci bne 5b 14362306a36Sopenharmony_ci 14462306a36Sopenharmony_ci /* enable DDR auto power saving */ 14562306a36Sopenharmony_ci ldr r7, [r11, #MX6Q_MMDC_MAPSR] 14662306a36Sopenharmony_ci bic r7, r7, #0x1 14762306a36Sopenharmony_ci str r7, [r11, #MX6Q_MMDC_MAPSR] 14862306a36Sopenharmony_ci 14962306a36Sopenharmony_ci .endm 15062306a36Sopenharmony_ci 15162306a36Sopenharmony_ciENTRY(imx6_suspend) 15262306a36Sopenharmony_ci ldr r1, [r0, #PM_INFO_PBASE_OFFSET] 15362306a36Sopenharmony_ci ldr r2, [r0, #PM_INFO_RESUME_ADDR_OFFSET] 15462306a36Sopenharmony_ci ldr r3, [r0, #PM_INFO_DDR_TYPE_OFFSET] 15562306a36Sopenharmony_ci ldr r4, [r0, #PM_INFO_PM_INFO_SIZE_OFFSET] 15662306a36Sopenharmony_ci 15762306a36Sopenharmony_ci /* 15862306a36Sopenharmony_ci * counting the resume address in iram 15962306a36Sopenharmony_ci * to set it in SRC register. 16062306a36Sopenharmony_ci */ 16162306a36Sopenharmony_ci ldr r6, =imx6_suspend 16262306a36Sopenharmony_ci ldr r7, =resume 16362306a36Sopenharmony_ci sub r7, r7, r6 16462306a36Sopenharmony_ci add r8, r1, r4 16562306a36Sopenharmony_ci add r9, r8, r7 16662306a36Sopenharmony_ci 16762306a36Sopenharmony_ci /* 16862306a36Sopenharmony_ci * make sure TLB contain the addr we want, 16962306a36Sopenharmony_ci * as we will access them after MMDC IO floated. 17062306a36Sopenharmony_ci */ 17162306a36Sopenharmony_ci 17262306a36Sopenharmony_ci ldr r11, [r0, #PM_INFO_MX6Q_CCM_V_OFFSET] 17362306a36Sopenharmony_ci ldr r6, [r11, #0x0] 17462306a36Sopenharmony_ci ldr r11, [r0, #PM_INFO_MX6Q_GPC_V_OFFSET] 17562306a36Sopenharmony_ci ldr r6, [r11, #0x0] 17662306a36Sopenharmony_ci ldr r11, [r0, #PM_INFO_MX6Q_IOMUXC_V_OFFSET] 17762306a36Sopenharmony_ci ldr r6, [r11, #0x0] 17862306a36Sopenharmony_ci 17962306a36Sopenharmony_ci /* use r11 to store the IO address */ 18062306a36Sopenharmony_ci ldr r11, [r0, #PM_INFO_MX6Q_SRC_V_OFFSET] 18162306a36Sopenharmony_ci /* store physical resume addr and pm_info address. */ 18262306a36Sopenharmony_ci str r9, [r11, #MX6Q_SRC_GPR1] 18362306a36Sopenharmony_ci str r1, [r11, #MX6Q_SRC_GPR2] 18462306a36Sopenharmony_ci 18562306a36Sopenharmony_ci /* need to sync L2 cache before DSM. */ 18662306a36Sopenharmony_ci sync_l2_cache 18762306a36Sopenharmony_ci 18862306a36Sopenharmony_ci ldr r11, [r0, #PM_INFO_MX6Q_MMDC_V_OFFSET] 18962306a36Sopenharmony_ci /* 19062306a36Sopenharmony_ci * put DDR explicitly into self-refresh and 19162306a36Sopenharmony_ci * disable automatic power savings. 19262306a36Sopenharmony_ci */ 19362306a36Sopenharmony_ci ldr r7, [r11, #MX6Q_MMDC_MAPSR] 19462306a36Sopenharmony_ci orr r7, r7, #0x1 19562306a36Sopenharmony_ci str r7, [r11, #MX6Q_MMDC_MAPSR] 19662306a36Sopenharmony_ci 19762306a36Sopenharmony_ci /* make the DDR explicitly enter self-refresh. */ 19862306a36Sopenharmony_ci ldr r7, [r11, #MX6Q_MMDC_MAPSR] 19962306a36Sopenharmony_ci orr r7, r7, #(1 << 21) 20062306a36Sopenharmony_ci str r7, [r11, #MX6Q_MMDC_MAPSR] 20162306a36Sopenharmony_ci 20262306a36Sopenharmony_cipoll_dvfs_set: 20362306a36Sopenharmony_ci ldr r7, [r11, #MX6Q_MMDC_MAPSR] 20462306a36Sopenharmony_ci ands r7, r7, #(1 << 25) 20562306a36Sopenharmony_ci beq poll_dvfs_set 20662306a36Sopenharmony_ci 20762306a36Sopenharmony_ci ldr r11, [r0, #PM_INFO_MX6Q_IOMUXC_V_OFFSET] 20862306a36Sopenharmony_ci ldr r6, =0x0 20962306a36Sopenharmony_ci ldr r7, [r0, #PM_INFO_MMDC_IO_NUM_OFFSET] 21062306a36Sopenharmony_ci ldr r8, =PM_INFO_MMDC_IO_VAL_OFFSET 21162306a36Sopenharmony_ci add r8, r8, r0 21262306a36Sopenharmony_ci /* LPDDR2's last 3 IOs need special setting */ 21362306a36Sopenharmony_ci cmp r3, #IMX_DDR_TYPE_LPDDR2 21462306a36Sopenharmony_ci subeq r7, r7, #0x3 21562306a36Sopenharmony_ciset_mmdc_io_lpm: 21662306a36Sopenharmony_ci ldr r9, [r8], #0x8 21762306a36Sopenharmony_ci str r6, [r11, r9] 21862306a36Sopenharmony_ci subs r7, r7, #0x1 21962306a36Sopenharmony_ci bne set_mmdc_io_lpm 22062306a36Sopenharmony_ci 22162306a36Sopenharmony_ci cmp r3, #IMX_DDR_TYPE_LPDDR2 22262306a36Sopenharmony_ci bne set_mmdc_io_lpm_done 22362306a36Sopenharmony_ci ldr r6, =0x1000 22462306a36Sopenharmony_ci ldr r9, [r8], #0x8 22562306a36Sopenharmony_ci str r6, [r11, r9] 22662306a36Sopenharmony_ci ldr r9, [r8], #0x8 22762306a36Sopenharmony_ci str r6, [r11, r9] 22862306a36Sopenharmony_ci ldr r6, =0x80000 22962306a36Sopenharmony_ci ldr r9, [r8] 23062306a36Sopenharmony_ci str r6, [r11, r9] 23162306a36Sopenharmony_ciset_mmdc_io_lpm_done: 23262306a36Sopenharmony_ci 23362306a36Sopenharmony_ci /* 23462306a36Sopenharmony_ci * mask all GPC interrupts before 23562306a36Sopenharmony_ci * enabling the RBC counters to 23662306a36Sopenharmony_ci * avoid the counter starting too 23762306a36Sopenharmony_ci * early if an interupt is already 23862306a36Sopenharmony_ci * pending. 23962306a36Sopenharmony_ci */ 24062306a36Sopenharmony_ci ldr r11, [r0, #PM_INFO_MX6Q_GPC_V_OFFSET] 24162306a36Sopenharmony_ci ldr r6, [r11, #MX6Q_GPC_IMR1] 24262306a36Sopenharmony_ci ldr r7, [r11, #MX6Q_GPC_IMR2] 24362306a36Sopenharmony_ci ldr r8, [r11, #MX6Q_GPC_IMR3] 24462306a36Sopenharmony_ci ldr r9, [r11, #MX6Q_GPC_IMR4] 24562306a36Sopenharmony_ci 24662306a36Sopenharmony_ci ldr r10, =0xffffffff 24762306a36Sopenharmony_ci str r10, [r11, #MX6Q_GPC_IMR1] 24862306a36Sopenharmony_ci str r10, [r11, #MX6Q_GPC_IMR2] 24962306a36Sopenharmony_ci str r10, [r11, #MX6Q_GPC_IMR3] 25062306a36Sopenharmony_ci str r10, [r11, #MX6Q_GPC_IMR4] 25162306a36Sopenharmony_ci 25262306a36Sopenharmony_ci /* 25362306a36Sopenharmony_ci * enable the RBC bypass counter here 25462306a36Sopenharmony_ci * to hold off the interrupts. RBC counter 25562306a36Sopenharmony_ci * = 32 (1ms), Minimum RBC delay should be 25662306a36Sopenharmony_ci * 400us for the analog LDOs to power down. 25762306a36Sopenharmony_ci */ 25862306a36Sopenharmony_ci ldr r11, [r0, #PM_INFO_MX6Q_CCM_V_OFFSET] 25962306a36Sopenharmony_ci ldr r10, [r11, #MX6Q_CCM_CCR] 26062306a36Sopenharmony_ci bic r10, r10, #(0x3f << 21) 26162306a36Sopenharmony_ci orr r10, r10, #(0x20 << 21) 26262306a36Sopenharmony_ci str r10, [r11, #MX6Q_CCM_CCR] 26362306a36Sopenharmony_ci 26462306a36Sopenharmony_ci /* enable the counter. */ 26562306a36Sopenharmony_ci ldr r10, [r11, #MX6Q_CCM_CCR] 26662306a36Sopenharmony_ci orr r10, r10, #(0x1 << 27) 26762306a36Sopenharmony_ci str r10, [r11, #MX6Q_CCM_CCR] 26862306a36Sopenharmony_ci 26962306a36Sopenharmony_ci /* unmask all the GPC interrupts. */ 27062306a36Sopenharmony_ci ldr r11, [r0, #PM_INFO_MX6Q_GPC_V_OFFSET] 27162306a36Sopenharmony_ci str r6, [r11, #MX6Q_GPC_IMR1] 27262306a36Sopenharmony_ci str r7, [r11, #MX6Q_GPC_IMR2] 27362306a36Sopenharmony_ci str r8, [r11, #MX6Q_GPC_IMR3] 27462306a36Sopenharmony_ci str r9, [r11, #MX6Q_GPC_IMR4] 27562306a36Sopenharmony_ci 27662306a36Sopenharmony_ci /* 27762306a36Sopenharmony_ci * now delay for a short while (3usec) 27862306a36Sopenharmony_ci * ARM is at 1GHz at this point 27962306a36Sopenharmony_ci * so a short loop should be enough. 28062306a36Sopenharmony_ci * this delay is required to ensure that 28162306a36Sopenharmony_ci * the RBC counter can start counting in 28262306a36Sopenharmony_ci * case an interrupt is already pending 28362306a36Sopenharmony_ci * or in case an interrupt arrives just 28462306a36Sopenharmony_ci * as ARM is about to assert DSM_request. 28562306a36Sopenharmony_ci */ 28662306a36Sopenharmony_ci ldr r6, =2000 28762306a36Sopenharmony_cirbc_loop: 28862306a36Sopenharmony_ci subs r6, r6, #0x1 28962306a36Sopenharmony_ci bne rbc_loop 29062306a36Sopenharmony_ci 29162306a36Sopenharmony_ci /* Zzz, enter stop mode */ 29262306a36Sopenharmony_ci wfi 29362306a36Sopenharmony_ci nop 29462306a36Sopenharmony_ci nop 29562306a36Sopenharmony_ci nop 29662306a36Sopenharmony_ci nop 29762306a36Sopenharmony_ci 29862306a36Sopenharmony_ci /* 29962306a36Sopenharmony_ci * run to here means there is pending 30062306a36Sopenharmony_ci * wakeup source, system should auto 30162306a36Sopenharmony_ci * resume, we need to restore MMDC IO first 30262306a36Sopenharmony_ci */ 30362306a36Sopenharmony_ci mov r5, #0x0 30462306a36Sopenharmony_ci resume_mmdc 30562306a36Sopenharmony_ci 30662306a36Sopenharmony_ci /* return to suspend finish */ 30762306a36Sopenharmony_ci ret lr 30862306a36Sopenharmony_ci 30962306a36Sopenharmony_ciresume: 31062306a36Sopenharmony_ci /* invalidate L1 I-cache first */ 31162306a36Sopenharmony_ci mov r6, #0x0 31262306a36Sopenharmony_ci mcr p15, 0, r6, c7, c5, 0 31362306a36Sopenharmony_ci mcr p15, 0, r6, c7, c5, 6 31462306a36Sopenharmony_ci /* enable the Icache and branch prediction */ 31562306a36Sopenharmony_ci mov r6, #0x1800 31662306a36Sopenharmony_ci mcr p15, 0, r6, c1, c0, 0 31762306a36Sopenharmony_ci isb 31862306a36Sopenharmony_ci 31962306a36Sopenharmony_ci /* get physical resume address from pm_info. */ 32062306a36Sopenharmony_ci ldr lr, [r0, #PM_INFO_RESUME_ADDR_OFFSET] 32162306a36Sopenharmony_ci /* clear core0's entry and parameter */ 32262306a36Sopenharmony_ci ldr r11, [r0, #PM_INFO_MX6Q_SRC_P_OFFSET] 32362306a36Sopenharmony_ci mov r7, #0x0 32462306a36Sopenharmony_ci str r7, [r11, #MX6Q_SRC_GPR1] 32562306a36Sopenharmony_ci str r7, [r11, #MX6Q_SRC_GPR2] 32662306a36Sopenharmony_ci 32762306a36Sopenharmony_ci ldr r3, [r0, #PM_INFO_DDR_TYPE_OFFSET] 32862306a36Sopenharmony_ci mov r5, #0x1 32962306a36Sopenharmony_ci resume_mmdc 33062306a36Sopenharmony_ci 33162306a36Sopenharmony_ci ret lr 33262306a36Sopenharmony_ciENDPROC(imx6_suspend) 333