/* * Copyright (c) 2014, Fuzhou Rockchip Electronics Co., Ltd * Author: Chris Zhong * * This program is free software; you can redistribute it and/or modify it * under the terms and conditions of the GNU General Public License, * version 2, as published by the Free Software Foundation. * * This program is distributed in the hope 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 /* BUILD_BUG_ON */ #include #include #include "rk3288_ddr.h" #include "rk3288_resume.h" #include "sram_delay.h" #include "../pm.h" #define PMU_ADDR 0xff730000 #define PMU_PWRMODE_CON_ADDR ((void *)(PMU_ADDR + RK3288_PMU_PWRMODE_CON)) #define DDR_PCTRL0_ADDR ((void *)0xff610000) #define DDR_PUBL0_ADDR ((void *)0xff620000) /* phy */ #define DDR_PCTRL1_ADDR ((void *)0xff630000) #define DDR_PUBL1_ADDR ((void *)0xff640000) /* phy */ #define MSCH0_ADDR ((void *)0xffac0000) #define MSCH1_ADDR ((void *)0xffac0080) static void * const pctrl_addrs[] = { DDR_PCTRL0_ADDR, DDR_PCTRL1_ADDR }; static void * const phy_addrs[] = { DDR_PUBL0_ADDR, DDR_PUBL1_ADDR }; static void * const msch_addrs[] = { MSCH0_ADDR, MSCH1_ADDR }; static void reset_dll(void __iomem *phy_addr) { static const u32 reg[] = { DDR_PUBL_ACDLLCR, DDR_PUBL_DX0DLLCR, DDR_PUBL_DX1DLLCR, DDR_PUBL_DX2DLLCR, DDR_PUBL_DX3DLLCR }; int i; for (i = 0; i < ARRAY_SIZE(reg); i++) writel_relaxed(readl_relaxed(phy_addr + reg[i]) & ~DLLSRST, phy_addr + reg[i]); sram_udelay(10); for (i = 0; i < ARRAY_SIZE(reg); i++) writel_relaxed(readl_relaxed(phy_addr + reg[i]) | DLLSRST, phy_addr + reg[i]); sram_udelay(10); } static void phy_init(void __iomem *phy_addr) { u32 val; val = readl_relaxed(phy_addr + DDR_PUBL_PIR); val |= PIR_INIT | PIR_DLLSRST | PIR_DLLLOCK | PIR_ZCAL | PIR_ITMSRST | PIR_CLRSR; writel_relaxed(val, phy_addr + DDR_PUBL_PIR); sram_udelay(1); while ((readl_relaxed(phy_addr + DDR_PUBL_PGSR) & (PGSR_IDONE | PGSR_DLDONE | PGSR_ZCDONE)) != (PGSR_IDONE | PGSR_DLDONE | PGSR_ZCDONE)) ; } static void memory_init(void __iomem *phy_addr) { u32 val; val = readl_relaxed(phy_addr + DDR_PUBL_PIR); val |= PIR_INIT | PIR_DRAMINIT | PIR_LOCKBYP | PIR_ZCALBYP | PIR_CLRSR | PIR_ICPC; writel_relaxed(val, phy_addr + DDR_PUBL_PIR); sram_udelay(1); while ((readl_relaxed(phy_addr + DDR_PUBL_PGSR) & (PGSR_IDONE | PGSR_DLDONE)) != (PGSR_IDONE | PGSR_DLDONE)) ; } static void move_to_lowpower_state(void __iomem *pctrl_addr, void __iomem *phy_addr) { u32 state; while (1) { state = readl_relaxed(pctrl_addr + DDR_PCTL_STAT) & PCTL_STAT_MSK; switch (state) { case INIT_MEM: writel_relaxed(CFG_STATE, pctrl_addr + DDR_PCTL_SCTL); while ((readl_relaxed(pctrl_addr + DDR_PCTL_STAT) & PCTL_STAT_MSK) != CONFIG) ; /* no break */ case CONFIG: writel_relaxed(GO_STATE, pctrl_addr + DDR_PCTL_SCTL); while ((readl_relaxed(pctrl_addr + DDR_PCTL_STAT) & PCTL_STAT_MSK) != ACCESS) ; /* no break */ case ACCESS: writel_relaxed(SLEEP_STATE, pctrl_addr + DDR_PCTL_SCTL); while ((readl_relaxed(pctrl_addr + DDR_PCTL_STAT) & PCTL_STAT_MSK) != LOW_POWER) ; /* no break */ case LOW_POWER: return; default: break; } } } static void move_to_access_state(void __iomem *pctrl_addr, void __iomem *phy_addr) { u32 state; while (1) { state = readl_relaxed(pctrl_addr + DDR_PCTL_STAT) & PCTL_STAT_MSK; switch (state) { case LOW_POWER: if (LP_TRIG_VAL(readl_relaxed(pctrl_addr + DDR_PCTL_STAT)) == 1) return; writel_relaxed(WAKEUP_STATE, pctrl_addr + DDR_PCTL_SCTL); while ((readl_relaxed(pctrl_addr + DDR_PCTL_STAT) & PCTL_STAT_MSK) != ACCESS) ; /* wait DLL lock */ while ((readl_relaxed(phy_addr + DDR_PUBL_PGSR) & PGSR_DLDONE) != PGSR_DLDONE) ; break; case INIT_MEM: writel_relaxed(CFG_STATE, pctrl_addr + DDR_PCTL_SCTL); while ((readl_relaxed(pctrl_addr + DDR_PCTL_STAT) & PCTL_STAT_MSK) != CONFIG) ; /* fallthrough here */ case CONFIG: writel_relaxed(GO_STATE, pctrl_addr + DDR_PCTL_SCTL); while ((readl_relaxed(pctrl_addr + DDR_PCTL_STAT) & PCTL_STAT_MSK) == CONFIG) ; break; case ACCESS: return; default: break; } } } static void rk3288_ddr_reg_restore(void __iomem *regbase, const u32 reg_list[], int num_reg, const u32 *vals) { int i; for (i = 0; i < num_reg && reg_list[i] != RK3288_BOGUS_OFFSET; i++) writel_relaxed(vals[i], regbase + reg_list[i]); } void rk3288_ddr_resume_early(const struct rk3288_ddr_save_data *ddr_save_data) { int ch; /* PWM saves full address, so base is NULL */ rk3288_ddr_reg_restore(NULL, ddr_save_data->pwm_addrs, RK3288_MAX_PWM_REGS, ddr_save_data->pwm_vals); /* * PWM never runs higher than 1.2V giving a 2000uV/us ramp delay since * we start from 1V. This is a very conservative ramp delay for the * regulator. */ sram_udelay(100); for (ch = 0; ch < ARRAY_SIZE(pctrl_addrs); ch++) { /* DLL bypass */ rk3288_ddr_reg_restore(phy_addrs[ch], ddr_save_data->phy_dll_offsets, RK3288_MAX_DDR_PHY_DLL_REGS, ddr_save_data->phy_dll_vals[ch]); reset_dll(phy_addrs[ch]); /* ddr ctrl restore; NOTE: both channels must be the same */ rk3288_ddr_reg_restore(pctrl_addrs[ch], ddr_save_data->ctrl_offsets, RK3288_MAX_DDR_CTRL_REGS, ddr_save_data->ctrl_vals); /* ddr phy restore */ rk3288_ddr_reg_restore(phy_addrs[ch], ddr_save_data->phy_offsets, RK3288_MAX_DDR_PHY_REGS, ddr_save_data->phy_vals[ch]); /* msch restore */ rk3288_ddr_reg_restore(msch_addrs[ch], ddr_save_data->msch_offsets, RK3288_MAX_DDR_MSCH_REGS, ddr_save_data->msch_vals[ch]); phy_init(phy_addrs[ch]); /* power up ddr power */ writel_relaxed(POWER_UP_START, pctrl_addrs[ch] + DDR_PCTL_POWCTL); while (!(readl_relaxed(pctrl_addrs[ch] + DDR_PCTL_POWSTAT) & POWER_UP_DONE)) ; /* zqcr restore; NOTE: both channels must be the same */ rk3288_ddr_reg_restore(phy_addrs[ch], ddr_save_data->phy_zqcr_offsets, RK3288_MAX_DDR_PHY_ZQCR_REGS, ddr_save_data->phy_zqcr_vals); memory_init(phy_addrs[ch]); move_to_lowpower_state(pctrl_addrs[ch], phy_addrs[ch]); } /* disable retention */ writel_relaxed(readl_relaxed(PMU_PWRMODE_CON_ADDR) | DDR0IO_RET_DE_REQ | DDR0I1_RET_DE_REQ, PMU_PWRMODE_CON_ADDR); sram_udelay(1); /* disable self-refresh */ for (ch = 0; ch < ARRAY_SIZE(pctrl_addrs); ch++) move_to_access_state(pctrl_addrs[ch], phy_addrs[ch]); }