ddr: imx: Add i.MX9 DDR controller driver

Since i.MX9 uses same DDR PHY with i.MX8M, split the DDRPHY to a common
directory under imx, then use dedicated ddr controller driver for each
iMX9 and iMX8M.

The DDRPHY registers are space compressed, so it needs conversion to
access the DDRPHY address. Introduce a common PHY address remap function
for both iMX8M and iMX9 for all PHY registers accessing.

Signed-off-by: Ye Li <ye.li@nxp.com>
Signed-off-by: Peng Fan <peng.fan@nxp.com>
This commit is contained in:
Ye Li 2022-07-26 16:41:07 +08:00 committed by Stefano Babic
parent e631185a20
commit 99c7cc58e1
16 changed files with 1077 additions and 25 deletions

View file

@ -725,6 +725,8 @@ void update_umctl2_rank_space_setting(unsigned int pstat_num);
void get_trained_CDD(unsigned int fsp);
unsigned int lpddr4_mr_read(unsigned int mr_rank, unsigned int mr_addr);
ulong ddrphy_addr_remap(uint32_t paddr_apb_from_ctlr);
static inline void reg32_write(unsigned long addr, u32 val)
{
writel(val, addr);
@ -741,9 +743,9 @@ static inline void reg32setbit(unsigned long addr, u32 bit)
}
#define dwc_ddrphy_apb_wr(addr, data) \
reg32_write(IP2APB_DDRPHY_IPS_BASE_ADDR(0) + 4 * (addr), data)
reg32_write(IP2APB_DDRPHY_IPS_BASE_ADDR(0) + ddrphy_addr_remap(addr), data)
#define dwc_ddrphy_apb_rd(addr) \
reg32_read(IP2APB_DDRPHY_IPS_BASE_ADDR(0) + 4 * (addr))
reg32_read(IP2APB_DDRPHY_IPS_BASE_ADDR(0) + ddrphy_addr_remap(addr))
extern struct dram_cfg_param ddrphy_trained_csr[];
extern uint32_t ddrphy_trained_csr_num;

View file

@ -0,0 +1,126 @@
/* SPDX-License-Identifier: GPL-2.0+ */
/*
* Copyright 2022 NXP
*/
#ifndef __ASM_ARCH_IMX8M_DDR_H
#define __ASM_ARCH_IMX8M_DDR_H
#include <asm/io.h>
#include <asm/types.h>
#define DDR_CTL_BASE 0x4E300000
#define DDR_PHY_BASE 0x4E100000
#define DDRMIX_BLK_CTRL_BASE 0x4E010000
#define REG_DDRDSR_2 (DDR_CTL_BASE + 0xB24)
#define REG_DDR_SDRAM_CFG (DDR_CTL_BASE + 0x110)
#define REG_DDR_DEBUG_19 (DDR_CTL_BASE + 0xF48)
#define SRC_BASE_ADDR (0x44460000)
#define SRC_DPHY_BASE_ADDR (SRC_BASE_ADDR + 0x1400)
#define REG_SRC_DPHY_SW_CTRL (SRC_DPHY_BASE_ADDR + 0x20)
#define REG_SRC_DPHY_SINGLE_RESET_SW_CTRL (SRC_DPHY_BASE_ADDR + 0x24)
#define IP2APB_DDRPHY_IPS_BASE_ADDR(X) (DDR_PHY_BASE + ((X) * 0x2000000))
#define DDRPHY_MEM(X) (DDR_PHY_BASE + ((X) * 0x2000000) + 0x50000)
/* PHY State */
enum pstate {
PS0,
PS1,
PS2,
PS3,
};
enum msg_response {
TRAIN_SUCCESS = 0x7,
TRAIN_STREAM_START = 0x8,
TRAIN_FAIL = 0xff,
};
/* user data type */
enum fw_type {
FW_1D_IMAGE,
FW_2D_IMAGE,
};
struct dram_cfg_param {
unsigned int reg;
unsigned int val;
};
struct dram_fsp_msg {
unsigned int drate;
enum fw_type fw_type;
struct dram_cfg_param *fsp_cfg;
unsigned int fsp_cfg_num;
};
struct dram_timing_info {
/* umctl2 config */
struct dram_cfg_param *ddrc_cfg;
unsigned int ddrc_cfg_num;
/* ddrphy config */
struct dram_cfg_param *ddrphy_cfg;
unsigned int ddrphy_cfg_num;
/* ddr fsp train info */
struct dram_fsp_msg *fsp_msg;
unsigned int fsp_msg_num;
/* ddr phy trained CSR */
struct dram_cfg_param *ddrphy_trained_csr;
unsigned int ddrphy_trained_csr_num;
/* ddr phy PIE */
struct dram_cfg_param *ddrphy_pie;
unsigned int ddrphy_pie_num;
/* initialized drate table */
unsigned int fsp_table[4];
};
extern struct dram_timing_info dram_timing;
void ddr_load_train_firmware(enum fw_type type);
int ddr_init(struct dram_timing_info *timing_info);
int ddr_cfg_phy(struct dram_timing_info *timing_info);
void load_lpddr4_phy_pie(void);
void ddrphy_trained_csr_save(struct dram_cfg_param *param, unsigned int num);
void dram_config_save(struct dram_timing_info *info, unsigned long base);
void board_dram_ecc_scrub(void);
void ddrc_inline_ecc_scrub(unsigned int start_address,
unsigned int range_address);
void ddrc_inline_ecc_scrub_end(unsigned int start_address,
unsigned int range_address);
/* utils function for ddr phy training */
int wait_ddrphy_training_complete(void);
void ddrphy_init_set_dfi_clk(unsigned int drate);
void ddrphy_init_read_msg_block(enum fw_type type);
void get_trained_CDD(unsigned int fsp);
ulong ddrphy_addr_remap(u32 paddr_apb_from_ctlr);
static inline void reg32_write(unsigned long addr, u32 val)
{
writel(val, addr);
}
static inline u32 reg32_read(unsigned long addr)
{
return readl(addr);
}
static inline void reg32setbit(unsigned long addr, u32 bit)
{
setbits_le32(addr, (1 << bit));
}
#define dwc_ddrphy_apb_wr(addr, data) \
reg32_write(IP2APB_DDRPHY_IPS_BASE_ADDR(0) + ddrphy_addr_remap(addr), data)
#define dwc_ddrphy_apb_rd(addr) \
reg32_read(IP2APB_DDRPHY_IPS_BASE_ADDR(0) + ddrphy_addr_remap(addr))
extern struct dram_cfg_param ddrphy_trained_csr[];
extern u32 ddrphy_trained_csr_num;
#endif

View file

@ -49,6 +49,7 @@ obj-$(CONFIG_ARMADA_XP) += ddr/marvell/axp/
obj-$(CONFIG_$(SPL_)ALTERA_SDRAM) += ddr/altera/
obj-$(CONFIG_ARCH_IMX8M) += ddr/imx/imx8m/
obj-$(CONFIG_IMX8ULP_DRAM) += ddr/imx/imx8ulp/
obj-$(CONFIG_ARCH_IMX9) += ddr/imx/imx9/
obj-$(CONFIG_SPL_DM_RESET) += reset/
obj-$(CONFIG_SPL_MUSB_NEW) += usb/musb-new/
obj-$(CONFIG_SPL_USB_GADGET) += usb/gadget/

View file

@ -1,2 +1,4 @@
source "drivers/ddr/imx/imx8m/Kconfig"
source "drivers/ddr/imx/imx8ulp/Kconfig"
source "drivers/ddr/imx/imx9/Kconfig"
source "drivers/ddr/imx/phy/Kconfig"

View file

@ -3,6 +3,7 @@ menu "i.MX8M DDR controllers"
config IMX8M_DRAM
bool "imx8m dram"
select IMX_SNPS_DDR_PHY
config IMX8M_LPDDR4
bool "imx8m lpddr4"

View file

@ -5,5 +5,6 @@
#
ifdef CONFIG_SPL_BUILD
obj-$(CONFIG_IMX8M_DRAM) += helper.o ddrphy_utils.o ddrphy_train.o ddrphy_csr.o ddr_init.o
obj-$(CONFIG_IMX8M_DRAM) += ddr_init.o
obj-y += ../phy/
endif

View file

@ -11,6 +11,11 @@
#include <asm/arch/clock.h>
#include <asm/arch/sys_proto.h>
static unsigned int g_cdd_rr_max[4];
static unsigned int g_cdd_rw_max[4];
static unsigned int g_cdd_wr_max[4];
static unsigned int g_cdd_ww_max[4];
void ddr_cfg_umctl2(struct dram_cfg_param *ddrc_cfg, int num)
{
int i = 0;
@ -91,6 +96,215 @@ void __weak board_dram_ecc_scrub(void)
{
}
void lpddr4_mr_write(unsigned int mr_rank, unsigned int mr_addr,
unsigned int mr_data)
{
unsigned int tmp;
/*
* 1. Poll MRSTAT.mr_wr_busy until it is 0.
* This checks that there is no outstanding MR transaction.
* No writes should be performed to MRCTRL0 and MRCTRL1 if
* MRSTAT.mr_wr_busy = 1.
*/
do {
tmp = reg32_read(DDRC_MRSTAT(0));
} while (tmp & 0x1);
/*
* 2. Write the MRCTRL0.mr_type, MRCTRL0.mr_addr, MRCTRL0.mr_rank and
* (for MRWs) MRCTRL1.mr_data to define the MR transaction.
*/
reg32_write(DDRC_MRCTRL0(0), (mr_rank << 4));
reg32_write(DDRC_MRCTRL1(0), (mr_addr << 8) | mr_data);
reg32setbit(DDRC_MRCTRL0(0), 31);
}
unsigned int lpddr4_mr_read(unsigned int mr_rank, unsigned int mr_addr)
{
unsigned int tmp;
reg32_write(DRC_PERF_MON_MRR0_DAT(0), 0x1);
do {
tmp = reg32_read(DDRC_MRSTAT(0));
} while (tmp & 0x1);
reg32_write(DDRC_MRCTRL0(0), (mr_rank << 4) | 0x1);
reg32_write(DDRC_MRCTRL1(0), (mr_addr << 8));
reg32setbit(DDRC_MRCTRL0(0), 31);
do {
tmp = reg32_read(DRC_PERF_MON_MRR0_DAT(0));
} while ((tmp & 0x8) == 0);
tmp = reg32_read(DRC_PERF_MON_MRR1_DAT(0));
tmp = tmp & 0xff;
reg32_write(DRC_PERF_MON_MRR0_DAT(0), 0x4);
return tmp;
}
static unsigned int look_for_max(unsigned int data[], unsigned int addr_start,
unsigned int addr_end)
{
unsigned int i, imax = 0;
for (i = addr_start; i <= addr_end; i++) {
if (((data[i] >> 7) == 0) && data[i] > imax)
imax = data[i];
}
return imax;
}
void get_trained_CDD(u32 fsp)
{
unsigned int i, ddr_type, tmp;
unsigned int cdd_cha[12], cdd_chb[12];
unsigned int cdd_cha_rr_max, cdd_cha_rw_max, cdd_cha_wr_max, cdd_cha_ww_max;
unsigned int cdd_chb_rr_max, cdd_chb_rw_max, cdd_chb_wr_max, cdd_chb_ww_max;
ddr_type = reg32_read(DDRC_MSTR(0)) & 0x3f;
if (ddr_type == 0x20) {
for (i = 0; i < 6; i++) {
tmp = reg32_read(IP2APB_DDRPHY_IPS_BASE_ADDR(0) + (0x54013 + i) * 4);
cdd_cha[i * 2] = tmp & 0xff;
cdd_cha[i * 2 + 1] = (tmp >> 8) & 0xff;
}
for (i = 0; i < 7; i++) {
tmp = reg32_read(IP2APB_DDRPHY_IPS_BASE_ADDR(0) + (0x5402c + i) * 4);
if (i == 0) {
cdd_cha[0] = (tmp >> 8) & 0xff;
} else if (i == 6) {
cdd_cha[11] = tmp & 0xff;
} else {
cdd_chb[i * 2 - 1] = tmp & 0xff;
cdd_chb[i * 2] = (tmp >> 8) & 0xff;
}
}
cdd_cha_rr_max = look_for_max(cdd_cha, 0, 1);
cdd_cha_rw_max = look_for_max(cdd_cha, 2, 5);
cdd_cha_wr_max = look_for_max(cdd_cha, 6, 9);
cdd_cha_ww_max = look_for_max(cdd_cha, 10, 11);
cdd_chb_rr_max = look_for_max(cdd_chb, 0, 1);
cdd_chb_rw_max = look_for_max(cdd_chb, 2, 5);
cdd_chb_wr_max = look_for_max(cdd_chb, 6, 9);
cdd_chb_ww_max = look_for_max(cdd_chb, 10, 11);
g_cdd_rr_max[fsp] =
cdd_cha_rr_max > cdd_chb_rr_max ? cdd_cha_rr_max : cdd_chb_rr_max;
g_cdd_rw_max[fsp] =
cdd_cha_rw_max > cdd_chb_rw_max ? cdd_cha_rw_max : cdd_chb_rw_max;
g_cdd_wr_max[fsp] =
cdd_cha_wr_max > cdd_chb_wr_max ? cdd_cha_wr_max : cdd_chb_wr_max;
g_cdd_ww_max[fsp] =
cdd_cha_ww_max > cdd_chb_ww_max ? cdd_cha_ww_max : cdd_chb_ww_max;
} else {
unsigned int ddr4_cdd[64];
for (i = 0; i < 29; i++) {
tmp = reg32_read(IP2APB_DDRPHY_IPS_BASE_ADDR(0) + (0x54012 + i) * 4);
ddr4_cdd[i * 2] = tmp & 0xff;
ddr4_cdd[i * 2 + 1] = (tmp >> 8) & 0xff;
}
g_cdd_rr_max[fsp] = look_for_max(ddr4_cdd, 1, 12);
g_cdd_ww_max[fsp] = look_for_max(ddr4_cdd, 13, 24);
g_cdd_rw_max[fsp] = look_for_max(ddr4_cdd, 25, 40);
g_cdd_wr_max[fsp] = look_for_max(ddr4_cdd, 41, 56);
}
}
void update_umctl2_rank_space_setting(unsigned int pstat_num)
{
unsigned int i, ddr_type;
unsigned int addr_slot, rdata, tmp, tmp_t;
unsigned int ddrc_w2r, ddrc_r2w, ddrc_wr_gap, ddrc_rd_gap;
ddr_type = reg32_read(DDRC_MSTR(0)) & 0x3f;
for (i = 0; i < pstat_num; i++) {
addr_slot = i ? (i + 1) * 0x1000 : 0;
if (ddr_type == 0x20) {
/* update r2w:[13:8], w2r:[5:0] */
rdata = reg32_read(DDRC_DRAMTMG2(0) + addr_slot);
ddrc_w2r = rdata & 0x3f;
if (is_imx8mp())
tmp = ddrc_w2r + (g_cdd_wr_max[i] >> 1);
else
tmp = ddrc_w2r + (g_cdd_wr_max[i] >> 1) + 1;
ddrc_w2r = (tmp > 0x3f) ? 0x3f : tmp;
ddrc_r2w = (rdata >> 8) & 0x3f;
if (is_imx8mp())
tmp = ddrc_r2w + (g_cdd_rw_max[i] >> 1);
else
tmp = ddrc_r2w + (g_cdd_rw_max[i] >> 1) + 1;
ddrc_r2w = (tmp > 0x3f) ? 0x3f : tmp;
tmp_t = (rdata & 0xffffc0c0) | (ddrc_r2w << 8) | ddrc_w2r;
reg32_write((DDRC_DRAMTMG2(0) + addr_slot), tmp_t);
} else {
/* update w2r:[5:0] */
rdata = reg32_read(DDRC_DRAMTMG9(0) + addr_slot);
ddrc_w2r = rdata & 0x3f;
if (is_imx8mp())
tmp = ddrc_w2r + (g_cdd_wr_max[i] >> 1);
else
tmp = ddrc_w2r + (g_cdd_wr_max[i] >> 1) + 1;
ddrc_w2r = (tmp > 0x3f) ? 0x3f : tmp;
tmp_t = (rdata & 0xffffffc0) | ddrc_w2r;
reg32_write((DDRC_DRAMTMG9(0) + addr_slot), tmp_t);
/* update r2w:[13:8] */
rdata = reg32_read(DDRC_DRAMTMG2(0) + addr_slot);
ddrc_r2w = (rdata >> 8) & 0x3f;
if (is_imx8mp())
tmp = ddrc_r2w + (g_cdd_rw_max[i] >> 1);
else
tmp = ddrc_r2w + (g_cdd_rw_max[i] >> 1) + 1;
ddrc_r2w = (tmp > 0x3f) ? 0x3f : tmp;
tmp_t = (rdata & 0xffffc0ff) | (ddrc_r2w << 8);
reg32_write((DDRC_DRAMTMG2(0) + addr_slot), tmp_t);
}
if (!is_imx8mq()) {
/*
* update rankctl: wr_gap:11:8; rd:gap:7:4; quasi-dymic, doc wrong(static)
*/
rdata = reg32_read(DDRC_RANKCTL(0) + addr_slot);
ddrc_wr_gap = (rdata >> 8) & 0xf;
if (is_imx8mp())
tmp = ddrc_wr_gap + (g_cdd_ww_max[i] >> 1);
else
tmp = ddrc_wr_gap + (g_cdd_ww_max[i] >> 1) + 1;
ddrc_wr_gap = (tmp > 0xf) ? 0xf : tmp;
ddrc_rd_gap = (rdata >> 4) & 0xf;
if (is_imx8mp())
tmp = ddrc_rd_gap + (g_cdd_rr_max[i] >> 1);
else
tmp = ddrc_rd_gap + (g_cdd_rr_max[i] >> 1) + 1;
ddrc_rd_gap = (tmp > 0xf) ? 0xf : tmp;
tmp_t = (rdata & 0xfffff00f) | (ddrc_wr_gap << 8) | (ddrc_rd_gap << 4);
reg32_write((DDRC_RANKCTL(0) + addr_slot), tmp_t);
}
}
if (is_imx8mq()) {
/* update rankctl: wr_gap:11:8; rd:gap:7:4; quasi-dymic, doc wrong(static) */
rdata = reg32_read(DDRC_RANKCTL(0));
ddrc_wr_gap = (rdata >> 8) & 0xf;
tmp = ddrc_wr_gap + (g_cdd_ww_max[0] >> 1) + 1;
ddrc_wr_gap = (tmp > 0xf) ? 0xf : tmp;
ddrc_rd_gap = (rdata >> 4) & 0xf;
tmp = ddrc_rd_gap + (g_cdd_rr_max[0] >> 1) + 1;
ddrc_rd_gap = (tmp > 0xf) ? 0xf : tmp;
tmp_t = (rdata & 0xfffff00f) | (ddrc_wr_gap << 8) | (ddrc_rd_gap << 4);
reg32_write(DDRC_RANKCTL(0), tmp_t);
}
}
int ddr_init(struct dram_timing_info *dram_timing)
{
unsigned int tmp, initial_drate, target_freq;
@ -250,3 +464,8 @@ int ddr_init(struct dram_timing_info *dram_timing)
return 0;
}
ulong ddrphy_addr_remap(uint32_t paddr_apb_from_ctlr)
{
return 4 * paddr_apb_from_ctlr;
}

View file

@ -0,0 +1,21 @@
menu "i.MX9 DDR controllers"
depends on ARCH_IMX9
config IMX9_DRAM
bool "imx9 dram"
select IMX_SNPS_DDR_PHY
config IMX9_LPDDR4X
bool "imx9 lpddr4 and lpddr4x"
select IMX9_DRAM
help
Select the i.MX9 LPDDR4/4X driver support on i.MX9 SOC.
config SAVED_DRAM_TIMING_BASE
hex "Define the base address for saved dram timing"
help
after DRAM is trained, need to save the dram related timming
info into memory for low power use.
default 0x204DC000
endmenu

View file

@ -0,0 +1,10 @@
#
# Copyright 2018 NXP
#
# SPDX-License-Identifier: GPL-2.0+
#
ifdef CONFIG_SPL_BUILD
obj-$(CONFIG_IMX9_DRAM) += ddr_init.o
obj-y += ../phy/
endif

View file

@ -0,0 +1,485 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright 2022 NXP
*/
#include <common.h>
#include <errno.h>
#include <log.h>
#include <asm/io.h>
#include <asm/arch/ddr.h>
#include <asm/arch/clock.h>
#include <asm/arch/sys_proto.h>
#include <linux/delay.h>
void ddrphy_coldreset(void)
{
/* dramphy_apb_n default 1 , assert -> 0, de_assert -> 1 */
/* dramphy_reset_n default 0 , assert -> 0, de_assert -> 1 */
/* dramphy_PwrOKIn default 0 , assert -> 1, de_assert -> 0 */
/* src_gen_dphy_apb_sw_rst_de_assert */
clrbits_le32(REG_SRC_DPHY_SW_CTRL, BIT(0));
/* src_gen_dphy_sw_rst_de_assert */
clrbits_le32(REG_SRC_DPHY_SINGLE_RESET_SW_CTRL, BIT(2));
/* src_gen_dphy_PwrOKIn_sw_rst_de_assert() */
setbits_le32(REG_SRC_DPHY_SINGLE_RESET_SW_CTRL, BIT(0));
mdelay(10);
/* src_gen_dphy_apb_sw_rst_assert */
setbits_le32(REG_SRC_DPHY_SW_CTRL, BIT(0));
/* src_gen_dphy_sw_rst_assert */
setbits_le32(REG_SRC_DPHY_SINGLE_RESET_SW_CTRL, BIT(2));
mdelay(10);
/* src_gen_dphy_PwrOKIn_sw_rst_assert */
clrbits_le32(REG_SRC_DPHY_SINGLE_RESET_SW_CTRL, BIT(0));
mdelay(10);
/* src_gen_dphy_apb_sw_rst_de_assert */
clrbits_le32(REG_SRC_DPHY_SW_CTRL, BIT(0));
/* src_gen_dphy_sw_rst_de_assert() */
clrbits_le32(REG_SRC_DPHY_SINGLE_RESET_SW_CTRL, BIT(2));
}
void check_ddrc_idle(void)
{
u32 regval;
do {
regval = readl(REG_DDRDSR_2);
if (regval & BIT(31))
break;
} while (1);
}
void check_dfi_init_complete(void)
{
u32 regval;
do {
regval = readl(REG_DDRDSR_2);
if (regval & BIT(2))
break;
} while (1);
setbits_le32(REG_DDRDSR_2, BIT(2));
}
void ddrc_config(struct dram_cfg_param *ddrc_config, int num)
{
int i = 0;
for (i = 0; i < num; i++) {
writel(ddrc_config->val, (ulong)ddrc_config->reg);
ddrc_config++;
}
}
void get_trained_CDD(u32 fsp)
{
}
int ddr_init(struct dram_timing_info *dram_timing)
{
unsigned int initial_drate;
int ret;
u32 regval;
debug("DDRINFO: start DRAM init\n");
/* reset ddrphy */
ddrphy_coldreset();
debug("DDRINFO: cfg clk\n");
initial_drate = dram_timing->fsp_msg[0].drate;
/* default to the frequency point 0 clock */
ddrphy_init_set_dfi_clk(initial_drate);
/*
* Start PHY initialization and training by
* accessing relevant PUB registers
*/
debug("DDRINFO:ddrphy config start\n");
ret = ddr_cfg_phy(dram_timing);
if (ret)
return ret;
debug("DDRINFO: ddrphy config done\n");
/* rogram the ddrc registers */
debug("DDRINFO: ddrc config start\n");
ddrc_config(dram_timing->ddrc_cfg, dram_timing->ddrc_cfg_num);
debug("DDRINFO: ddrc config done\n");
check_dfi_init_complete();
regval = readl(REG_DDR_SDRAM_CFG);
writel((regval | 0x80000000), REG_DDR_SDRAM_CFG);
check_ddrc_idle();
/* save the dram timing config into memory */
dram_config_save(dram_timing, CONFIG_SAVED_DRAM_TIMING_BASE);
return 0;
}
ulong ddrphy_addr_remap(u32 paddr_apb_from_ctlr)
{
u32 paddr_apb_qual;
u32 paddr_apb_unqual_dec_22_13;
u32 paddr_apb_unqual_dec_19_13;
u32 paddr_apb_unqual_dec_12_1;
u32 paddr_apb_unqual;
u32 paddr_apb_phy;
paddr_apb_qual = (paddr_apb_from_ctlr << 1);
paddr_apb_unqual_dec_22_13 = ((paddr_apb_qual & 0x7fe000) >> 13);
paddr_apb_unqual_dec_12_1 = ((paddr_apb_qual & 0x1ffe) >> 1);
switch (paddr_apb_unqual_dec_22_13) {
case 0x000:
paddr_apb_unqual_dec_19_13 = 0x00;
break;
case 0x001:
paddr_apb_unqual_dec_19_13 = 0x01;
break;
case 0x002:
paddr_apb_unqual_dec_19_13 = 0x02;
break;
case 0x003:
paddr_apb_unqual_dec_19_13 = 0x03;
break;
case 0x004:
paddr_apb_unqual_dec_19_13 = 0x04;
break;
case 0x005:
paddr_apb_unqual_dec_19_13 = 0x05;
break;
case 0x006:
paddr_apb_unqual_dec_19_13 = 0x06;
break;
case 0x007:
paddr_apb_unqual_dec_19_13 = 0x07;
break;
case 0x008:
paddr_apb_unqual_dec_19_13 = 0x08;
break;
case 0x009:
paddr_apb_unqual_dec_19_13 = 0x09;
break;
case 0x00a:
paddr_apb_unqual_dec_19_13 = 0x0a;
break;
case 0x00b:
paddr_apb_unqual_dec_19_13 = 0x0b;
break;
case 0x100:
paddr_apb_unqual_dec_19_13 = 0x0c;
break;
case 0x101:
paddr_apb_unqual_dec_19_13 = 0x0d;
break;
case 0x102:
paddr_apb_unqual_dec_19_13 = 0x0e;
break;
case 0x103:
paddr_apb_unqual_dec_19_13 = 0x0f;
break;
case 0x104:
paddr_apb_unqual_dec_19_13 = 0x10;
break;
case 0x105:
paddr_apb_unqual_dec_19_13 = 0x11;
break;
case 0x106:
paddr_apb_unqual_dec_19_13 = 0x12;
break;
case 0x107:
paddr_apb_unqual_dec_19_13 = 0x13;
break;
case 0x108:
paddr_apb_unqual_dec_19_13 = 0x14;
break;
case 0x109:
paddr_apb_unqual_dec_19_13 = 0x15;
break;
case 0x10a:
paddr_apb_unqual_dec_19_13 = 0x16;
break;
case 0x10b:
paddr_apb_unqual_dec_19_13 = 0x17;
break;
case 0x200:
paddr_apb_unqual_dec_19_13 = 0x18;
break;
case 0x201:
paddr_apb_unqual_dec_19_13 = 0x19;
break;
case 0x202:
paddr_apb_unqual_dec_19_13 = 0x1a;
break;
case 0x203:
paddr_apb_unqual_dec_19_13 = 0x1b;
break;
case 0x204:
paddr_apb_unqual_dec_19_13 = 0x1c;
break;
case 0x205:
paddr_apb_unqual_dec_19_13 = 0x1d;
break;
case 0x206:
paddr_apb_unqual_dec_19_13 = 0x1e;
break;
case 0x207:
paddr_apb_unqual_dec_19_13 = 0x1f;
break;
case 0x208:
paddr_apb_unqual_dec_19_13 = 0x20;
break;
case 0x209:
paddr_apb_unqual_dec_19_13 = 0x21;
break;
case 0x20a:
paddr_apb_unqual_dec_19_13 = 0x22;
break;
case 0x20b:
paddr_apb_unqual_dec_19_13 = 0x23;
break;
case 0x300:
paddr_apb_unqual_dec_19_13 = 0x24;
break;
case 0x301:
paddr_apb_unqual_dec_19_13 = 0x25;
break;
case 0x302:
paddr_apb_unqual_dec_19_13 = 0x26;
break;
case 0x303:
paddr_apb_unqual_dec_19_13 = 0x27;
break;
case 0x304:
paddr_apb_unqual_dec_19_13 = 0x28;
break;
case 0x305:
paddr_apb_unqual_dec_19_13 = 0x29;
break;
case 0x306:
paddr_apb_unqual_dec_19_13 = 0x2a;
break;
case 0x307:
paddr_apb_unqual_dec_19_13 = 0x2b;
break;
case 0x308:
paddr_apb_unqual_dec_19_13 = 0x2c;
break;
case 0x309:
paddr_apb_unqual_dec_19_13 = 0x2d;
break;
case 0x30a:
paddr_apb_unqual_dec_19_13 = 0x2e;
break;
case 0x30b:
paddr_apb_unqual_dec_19_13 = 0x2f;
break;
case 0x010:
paddr_apb_unqual_dec_19_13 = 0x30;
break;
case 0x011:
paddr_apb_unqual_dec_19_13 = 0x31;
break;
case 0x012:
paddr_apb_unqual_dec_19_13 = 0x32;
break;
case 0x013:
paddr_apb_unqual_dec_19_13 = 0x33;
break;
case 0x014:
paddr_apb_unqual_dec_19_13 = 0x34;
break;
case 0x015:
paddr_apb_unqual_dec_19_13 = 0x35;
break;
case 0x016:
paddr_apb_unqual_dec_19_13 = 0x36;
break;
case 0x017:
paddr_apb_unqual_dec_19_13 = 0x37;
break;
case 0x018:
paddr_apb_unqual_dec_19_13 = 0x38;
break;
case 0x019:
paddr_apb_unqual_dec_19_13 = 0x39;
break;
case 0x110:
paddr_apb_unqual_dec_19_13 = 0x3a;
break;
case 0x111:
paddr_apb_unqual_dec_19_13 = 0x3b;
break;
case 0x112:
paddr_apb_unqual_dec_19_13 = 0x3c;
break;
case 0x113:
paddr_apb_unqual_dec_19_13 = 0x3d;
break;
case 0x114:
paddr_apb_unqual_dec_19_13 = 0x3e;
break;
case 0x115:
paddr_apb_unqual_dec_19_13 = 0x3f;
break;
case 0x116:
paddr_apb_unqual_dec_19_13 = 0x40;
break;
case 0x117:
paddr_apb_unqual_dec_19_13 = 0x41;
break;
case 0x118:
paddr_apb_unqual_dec_19_13 = 0x42;
break;
case 0x119:
paddr_apb_unqual_dec_19_13 = 0x43;
break;
case 0x210:
paddr_apb_unqual_dec_19_13 = 0x44;
break;
case 0x211:
paddr_apb_unqual_dec_19_13 = 0x45;
break;
case 0x212:
paddr_apb_unqual_dec_19_13 = 0x46;
break;
case 0x213:
paddr_apb_unqual_dec_19_13 = 0x47;
break;
case 0x214:
paddr_apb_unqual_dec_19_13 = 0x48;
break;
case 0x215:
paddr_apb_unqual_dec_19_13 = 0x49;
break;
case 0x216:
paddr_apb_unqual_dec_19_13 = 0x4a;
break;
case 0x217:
paddr_apb_unqual_dec_19_13 = 0x4b;
break;
case 0x218:
paddr_apb_unqual_dec_19_13 = 0x4c;
break;
case 0x219:
paddr_apb_unqual_dec_19_13 = 0x4d;
break;
case 0x310:
paddr_apb_unqual_dec_19_13 = 0x4e;
break;
case 0x311:
paddr_apb_unqual_dec_19_13 = 0x4f;
break;
case 0x312:
paddr_apb_unqual_dec_19_13 = 0x50;
break;
case 0x313:
paddr_apb_unqual_dec_19_13 = 0x51;
break;
case 0x314:
paddr_apb_unqual_dec_19_13 = 0x52;
break;
case 0x315:
paddr_apb_unqual_dec_19_13 = 0x53;
break;
case 0x316:
paddr_apb_unqual_dec_19_13 = 0x54;
break;
case 0x317:
paddr_apb_unqual_dec_19_13 = 0x55;
break;
case 0x318:
paddr_apb_unqual_dec_19_13 = 0x56;
break;
case 0x319:
paddr_apb_unqual_dec_19_13 = 0x57;
break;
case 0x020:
paddr_apb_unqual_dec_19_13 = 0x58;
break;
case 0x120:
paddr_apb_unqual_dec_19_13 = 0x59;
break;
case 0x220:
paddr_apb_unqual_dec_19_13 = 0x5a;
break;
case 0x320:
paddr_apb_unqual_dec_19_13 = 0x5b;
break;
case 0x040:
paddr_apb_unqual_dec_19_13 = 0x5c;
break;
case 0x140:
paddr_apb_unqual_dec_19_13 = 0x5d;
break;
case 0x240:
paddr_apb_unqual_dec_19_13 = 0x5e;
break;
case 0x340:
paddr_apb_unqual_dec_19_13 = 0x5f;
break;
case 0x050:
paddr_apb_unqual_dec_19_13 = 0x60;
break;
case 0x051:
paddr_apb_unqual_dec_19_13 = 0x61;
break;
case 0x052:
paddr_apb_unqual_dec_19_13 = 0x62;
break;
case 0x053:
paddr_apb_unqual_dec_19_13 = 0x63;
break;
case 0x054:
paddr_apb_unqual_dec_19_13 = 0x64;
break;
case 0x055:
paddr_apb_unqual_dec_19_13 = 0x65;
break;
case 0x056:
paddr_apb_unqual_dec_19_13 = 0x66;
break;
case 0x057:
paddr_apb_unqual_dec_19_13 = 0x67;
break;
case 0x070:
paddr_apb_unqual_dec_19_13 = 0x68;
break;
case 0x090:
paddr_apb_unqual_dec_19_13 = 0x69;
break;
case 0x190:
paddr_apb_unqual_dec_19_13 = 0x6a;
break;
case 0x290:
paddr_apb_unqual_dec_19_13 = 0x6b;
break;
case 0x390:
paddr_apb_unqual_dec_19_13 = 0x6c;
break;
case 0x0c0:
paddr_apb_unqual_dec_19_13 = 0x6d;
break;
case 0x0d0:
paddr_apb_unqual_dec_19_13 = 0x6e;
break;
default:
paddr_apb_unqual_dec_19_13 = 0x00;
break;
}
paddr_apb_unqual = ((paddr_apb_unqual_dec_19_13 << 13) | (paddr_apb_unqual_dec_12_1 << 1));
paddr_apb_phy = (paddr_apb_unqual << 1);
return paddr_apb_phy;
}

View file

@ -0,0 +1,4 @@
config IMX_SNPS_DDR_PHY
bool "i.MX Snopsys DDR PHY"
help
Select the DDR PHY driver support on i.MX8M and i.MX9 SOC.

View file

@ -0,0 +1,9 @@
#
# Copyright 2018 NXP
#
# SPDX-License-Identifier: GPL-2.0+
#
ifdef CONFIG_SPL_BUILD
obj-$(CONFIG_IMX_SNPS_DDR_PHY) += helper.o ddrphy_utils.o ddrphy_train.o ddrphy_csr.o
endif

View file

@ -7,7 +7,6 @@
#include <log.h>
#include <linux/kernel.h>
#include <asm/arch/ddr.h>
#include <asm/arch/lpddr4_define.h>
#include <asm/arch/sys_proto.h>
int ddr_cfg_phy(struct dram_timing_info *dram_timing)

View file

@ -0,0 +1,169 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright 2018 NXP
*/
#include <common.h>
#include <errno.h>
#include <log.h>
#include <asm/io.h>
#include <asm/arch/ddr.h>
#include <asm/arch/clock.h>
#include <asm/arch/ddr.h>
#include <asm/arch/sys_proto.h>
static inline void poll_pmu_message_ready(void)
{
unsigned int reg;
do {
reg = reg32_read(IP2APB_DDRPHY_IPS_BASE_ADDR(0) + ddrphy_addr_remap(0xd0004));
} while (reg & 0x1);
}
static inline void ack_pmu_message_receive(void)
{
unsigned int reg;
reg32_write(IP2APB_DDRPHY_IPS_BASE_ADDR(0) + ddrphy_addr_remap(0xd0031), 0x0);
do {
reg = reg32_read(IP2APB_DDRPHY_IPS_BASE_ADDR(0) + ddrphy_addr_remap(0xd0004));
} while (!(reg & 0x1));
reg32_write(IP2APB_DDRPHY_IPS_BASE_ADDR(0) + ddrphy_addr_remap(0xd0031), 0x1);
}
static inline unsigned int get_mail(void)
{
unsigned int reg;
poll_pmu_message_ready();
reg = reg32_read(IP2APB_DDRPHY_IPS_BASE_ADDR(0) + ddrphy_addr_remap(0xd0032));
ack_pmu_message_receive();
return reg;
}
static inline unsigned int get_stream_message(void)
{
unsigned int reg, reg2;
poll_pmu_message_ready();
reg = reg32_read(IP2APB_DDRPHY_IPS_BASE_ADDR(0) + ddrphy_addr_remap(0xd0032));
reg2 = reg32_read(IP2APB_DDRPHY_IPS_BASE_ADDR(0) + ddrphy_addr_remap(0xd0034));
reg2 = (reg2 << 16) | reg;
ack_pmu_message_receive();
return reg2;
}
static inline void decode_major_message(unsigned int mail)
{
debug("[PMU Major message = 0x%08x]\n", mail);
}
static inline void decode_streaming_message(void)
{
unsigned int string_index, arg __maybe_unused;
int i = 0;
string_index = get_stream_message();
debug("PMU String index = 0x%08x\n", string_index);
while (i < (string_index & 0xffff)) {
arg = get_stream_message();
debug("arg[%d] = 0x%08x\n", i, arg);
i++;
}
debug("\n");
}
int wait_ddrphy_training_complete(void)
{
unsigned int mail;
while (1) {
mail = get_mail();
decode_major_message(mail);
if (mail == 0x08) {
decode_streaming_message();
} else if (mail == 0x07) {
debug("Training PASS\n");
return 0;
} else if (mail == 0xff) {
printf("Training FAILED\n");
return -1;
}
}
}
void ddrphy_init_set_dfi_clk(unsigned int drate)
{
switch (drate) {
case 4000:
dram_pll_init(MHZ(1000));
dram_disable_bypass();
break;
case 3733:
dram_pll_init(MHZ(933));
dram_disable_bypass();
break;
case 3200:
dram_pll_init(MHZ(800));
dram_disable_bypass();
break;
case 3000:
dram_pll_init(MHZ(750));
dram_disable_bypass();
break;
case 2800:
dram_pll_init(MHZ(700));
dram_disable_bypass();
break;
case 2400:
dram_pll_init(MHZ(600));
dram_disable_bypass();
break;
case 1866:
dram_pll_init(MHZ(466));
dram_disable_bypass();
break;
case 1600:
dram_pll_init(MHZ(400));
dram_disable_bypass();
break;
case 1066:
dram_pll_init(MHZ(266));
dram_disable_bypass();
break;
case 667:
dram_pll_init(MHZ(167));
dram_disable_bypass();
break;
case 400:
dram_enable_bypass(MHZ(400));
break;
case 333:
dram_enable_bypass(MHZ(333));
break;
case 200:
dram_enable_bypass(MHZ(200));
break;
case 100:
dram_enable_bypass(MHZ(100));
break;
default:
return;
}
}
void ddrphy_init_read_msg_block(enum fw_type type)
{
}

View file

@ -12,7 +12,6 @@
#include <asm/io.h>
#include <asm/arch/ddr.h>
#include <asm/arch/ddr.h>
#include <asm/arch/lpddr4_define.h>
#include <asm/sections.h>
DECLARE_GLOBAL_DATA_PTR;
@ -46,43 +45,46 @@ void ddr_load_train_firmware(enum fw_type type)
dmem_start = imem_start + IMEM_LEN;
pr_from32 = imem_start;
pr_to32 = DDR_TRAIN_CODE_BASE_ADDR + 4 * IMEM_OFFSET_ADDR;
pr_to32 = IMEM_OFFSET_ADDR;
for (i = 0x0; i < IMEM_LEN; ) {
tmp32 = readl(pr_from32);
writew(tmp32 & 0x0000ffff, pr_to32);
pr_to32 += 4;
writew((tmp32 >> 16) & 0x0000ffff, pr_to32);
pr_to32 += 4;
writew(tmp32 & 0x0000ffff, DDR_TRAIN_CODE_BASE_ADDR + ddrphy_addr_remap(pr_to32));
pr_to32 += 1;
writew((tmp32 >> 16) & 0x0000ffff,
DDR_TRAIN_CODE_BASE_ADDR + ddrphy_addr_remap(pr_to32));
pr_to32 += 1;
pr_from32 += 4;
i += 4;
}
pr_from32 = dmem_start;
pr_to32 = DDR_TRAIN_CODE_BASE_ADDR + 4 * DMEM_OFFSET_ADDR;
pr_to32 = DMEM_OFFSET_ADDR;
for (i = 0x0; i < DMEM_LEN; ) {
tmp32 = readl(pr_from32);
writew(tmp32 & 0x0000ffff, pr_to32);
pr_to32 += 4;
writew((tmp32 >> 16) & 0x0000ffff, pr_to32);
pr_to32 += 4;
writew(tmp32 & 0x0000ffff, DDR_TRAIN_CODE_BASE_ADDR + ddrphy_addr_remap(pr_to32));
pr_to32 += 1;
writew((tmp32 >> 16) & 0x0000ffff,
DDR_TRAIN_CODE_BASE_ADDR + ddrphy_addr_remap(pr_to32));
pr_to32 += 1;
pr_from32 += 4;
i += 4;
}
debug("check ddr_pmu_train_imem code\n");
pr_from32 = imem_start;
pr_to32 = DDR_TRAIN_CODE_BASE_ADDR + 4 * IMEM_OFFSET_ADDR;
pr_to32 = IMEM_OFFSET_ADDR;
for (i = 0x0; i < IMEM_LEN; ) {
tmp32 = (readw(pr_to32) & 0x0000ffff);
pr_to32 += 4;
tmp32 += ((readw(pr_to32) & 0x0000ffff) << 16);
tmp32 = (readw(DDR_TRAIN_CODE_BASE_ADDR + ddrphy_addr_remap(pr_to32)) & 0x0000ffff);
pr_to32 += 1;
tmp32 += ((readw(DDR_TRAIN_CODE_BASE_ADDR +
ddrphy_addr_remap(pr_to32)) & 0x0000ffff) << 16);
if (tmp32 != readl(pr_from32)) {
debug("%lx %lx\n", pr_from32, pr_to32);
error++;
}
pr_from32 += 4;
pr_to32 += 4;
pr_to32 += 1;
i += 4;
}
if (error)
@ -92,17 +94,18 @@ void ddr_load_train_firmware(enum fw_type type)
debug("check ddr4_pmu_train_dmem code\n");
pr_from32 = dmem_start;
pr_to32 = DDR_TRAIN_CODE_BASE_ADDR + 4 * DMEM_OFFSET_ADDR;
pr_to32 = DMEM_OFFSET_ADDR;
for (i = 0x0; i < DMEM_LEN;) {
tmp32 = (readw(pr_to32) & 0x0000ffff);
pr_to32 += 4;
tmp32 += ((readw(pr_to32) & 0x0000ffff) << 16);
tmp32 = (readw(DDR_TRAIN_CODE_BASE_ADDR + ddrphy_addr_remap(pr_to32)) & 0x0000ffff);
pr_to32 += 1;
tmp32 += ((readw(DDR_TRAIN_CODE_BASE_ADDR +
ddrphy_addr_remap(pr_to32)) & 0x0000ffff) << 16);
if (tmp32 != readl(pr_from32)) {
debug("%lx %lx\n", pr_from32, pr_to32);
error++;
}
pr_from32 += 4;
pr_to32 += 4;
pr_to32 += 1;
i += 4;
}