2025-05-10 21:58:58 +08:00

262 lines
6.8 KiB
C

/*
* Copyright (c) 2014, Fuzhou Rockchip Electronics Co., Ltd
* Author: Chris Zhong <zyw@rock-chips.com>
*
* 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 <linux/bug.h> /* BUILD_BUG_ON */
#include <linux/kernel.h>
#include <asm/io.h>
#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]);
}