2018-05-06 21:58:06 +00:00
|
|
|
// SPDX-License-Identifier: GPL-2.0+
|
2014-12-19 11:20:52 +00:00
|
|
|
/*
|
2016-10-27 14:47:07 +00:00
|
|
|
* Copyright (C) 2011-2014 Panasonic Corporation
|
|
|
|
* Copyright (C) 2015-2016 Socionext Inc.
|
2014-12-19 11:20:52 +00:00
|
|
|
*/
|
|
|
|
|
2017-10-22 15:19:36 +00:00
|
|
|
#include <linux/bitops.h>
|
|
|
|
#include <linux/delay.h>
|
2017-01-21 09:05:24 +00:00
|
|
|
#include <linux/errno.h>
|
2015-05-29 08:30:00 +00:00
|
|
|
#include <linux/io.h>
|
2017-10-22 15:19:36 +00:00
|
|
|
#include <linux/kernel.h>
|
|
|
|
#include <linux/printk.h>
|
|
|
|
#include <time.h>
|
2016-01-08 16:51:13 +00:00
|
|
|
|
2016-10-27 14:47:07 +00:00
|
|
|
#include "ddrphy-init.h"
|
2016-01-08 16:51:13 +00:00
|
|
|
#include "ddrphy-regs.h"
|
2014-12-19 11:20:52 +00:00
|
|
|
|
2016-10-27 14:47:08 +00:00
|
|
|
/* for LD4, Pro4, sLD8 */
|
|
|
|
#define NR_DATX8_PER_DDRPHY 2
|
|
|
|
|
2016-10-27 14:47:07 +00:00
|
|
|
void ddrphy_prepare_training(void __iomem *phy_base, int rank)
|
2014-12-19 11:20:52 +00:00
|
|
|
{
|
2016-10-27 14:47:07 +00:00
|
|
|
void __iomem *dx_base = phy_base + PHY_DX_BASE;
|
2014-12-19 11:20:52 +00:00
|
|
|
int dx;
|
2016-10-27 14:47:07 +00:00
|
|
|
u32 tmp;
|
2014-12-19 11:20:52 +00:00
|
|
|
|
|
|
|
for (dx = 0; dx < NR_DATX8_PER_DDRPHY; dx++) {
|
2016-10-27 14:47:07 +00:00
|
|
|
tmp = readl(dx_base + PHY_DX_GCR);
|
2014-12-19 11:20:52 +00:00
|
|
|
/* Specify the rank that should be write leveled */
|
2016-10-27 14:47:07 +00:00
|
|
|
tmp &= ~PHY_DX_GCR_WLRKEN_MASK;
|
|
|
|
tmp |= (1 << (PHY_DX_GCR_WLRKEN_SHIFT + rank)) &
|
|
|
|
PHY_DX_GCR_WLRKEN_MASK;
|
|
|
|
writel(tmp, dx_base + PHY_DX_GCR);
|
|
|
|
dx_base += PHY_DX_STRIDE;
|
2014-12-19 11:20:52 +00:00
|
|
|
}
|
|
|
|
|
2016-10-27 14:47:07 +00:00
|
|
|
tmp = readl(phy_base + PHY_DTCR);
|
2014-12-19 11:20:52 +00:00
|
|
|
/* Specify the rank used during data bit deskew and eye centering */
|
2016-10-27 14:47:07 +00:00
|
|
|
tmp &= ~PHY_DTCR_DTRANK_MASK;
|
|
|
|
tmp |= (rank << PHY_DTCR_DTRANK_SHIFT) & PHY_DTCR_DTRANK_MASK;
|
2014-12-19 11:20:52 +00:00
|
|
|
/* Use Multi-Purpose Register for DQS gate training */
|
2016-10-27 14:47:07 +00:00
|
|
|
tmp |= PHY_DTCR_DTMPR;
|
2014-12-19 11:20:52 +00:00
|
|
|
/* Specify the rank enabled for data-training */
|
2016-10-27 14:47:07 +00:00
|
|
|
tmp &= ~PHY_DTCR_RANKEN_MASK;
|
|
|
|
tmp |= (1 << (PHY_DTCR_RANKEN_SHIFT + rank)) & PHY_DTCR_RANKEN_MASK;
|
|
|
|
writel(tmp, phy_base + PHY_DTCR);
|
2014-12-19 11:20:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
struct ddrphy_init_sequence {
|
|
|
|
char *description;
|
|
|
|
u32 init_flag;
|
|
|
|
u32 done_flag;
|
|
|
|
u32 err_flag;
|
|
|
|
};
|
|
|
|
|
2015-12-16 01:36:13 +00:00
|
|
|
static const struct ddrphy_init_sequence init_sequence[] = {
|
2014-12-19 11:20:52 +00:00
|
|
|
{
|
|
|
|
"DRAM Initialization",
|
2016-10-27 14:47:07 +00:00
|
|
|
PHY_PIR_DRAMRST | PHY_PIR_DRAMINIT,
|
|
|
|
PHY_PGSR0_DIDONE,
|
|
|
|
PHY_PGSR0_DIERR
|
2014-12-19 11:20:52 +00:00
|
|
|
},
|
|
|
|
{
|
|
|
|
"Write Leveling",
|
2016-10-27 14:47:07 +00:00
|
|
|
PHY_PIR_WL,
|
|
|
|
PHY_PGSR0_WLDONE,
|
|
|
|
PHY_PGSR0_WLERR
|
2014-12-19 11:20:52 +00:00
|
|
|
},
|
|
|
|
{
|
|
|
|
"Read DQS Gate Training",
|
2016-10-27 14:47:07 +00:00
|
|
|
PHY_PIR_QSGATE,
|
|
|
|
PHY_PGSR0_QSGDONE,
|
|
|
|
PHY_PGSR0_QSGERR
|
2014-12-19 11:20:52 +00:00
|
|
|
},
|
|
|
|
{
|
|
|
|
"Write Leveling Adjustment",
|
2016-10-27 14:47:07 +00:00
|
|
|
PHY_PIR_WLADJ,
|
|
|
|
PHY_PGSR0_WLADONE,
|
|
|
|
PHY_PGSR0_WLAERR
|
2014-12-19 11:20:52 +00:00
|
|
|
},
|
|
|
|
{
|
|
|
|
"Read Bit Deskew",
|
2016-10-27 14:47:07 +00:00
|
|
|
PHY_PIR_RDDSKW,
|
|
|
|
PHY_PGSR0_RDDONE,
|
|
|
|
PHY_PGSR0_RDERR
|
2014-12-19 11:20:52 +00:00
|
|
|
},
|
|
|
|
{
|
|
|
|
"Write Bit Deskew",
|
2016-10-27 14:47:07 +00:00
|
|
|
PHY_PIR_WRDSKW,
|
|
|
|
PHY_PGSR0_WDDONE,
|
|
|
|
PHY_PGSR0_WDERR
|
2014-12-19 11:20:52 +00:00
|
|
|
},
|
|
|
|
{
|
|
|
|
"Read Eye Training",
|
2016-10-27 14:47:07 +00:00
|
|
|
PHY_PIR_RDEYE,
|
|
|
|
PHY_PGSR0_REDONE,
|
|
|
|
PHY_PGSR0_REERR
|
2014-12-19 11:20:52 +00:00
|
|
|
},
|
|
|
|
{
|
|
|
|
"Write Eye Training",
|
2016-10-27 14:47:07 +00:00
|
|
|
PHY_PIR_WREYE,
|
|
|
|
PHY_PGSR0_WEDONE,
|
|
|
|
PHY_PGSR0_WEERR
|
2014-12-19 11:20:52 +00:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2016-10-27 14:47:07 +00:00
|
|
|
int ddrphy_training(void __iomem *phy_base)
|
2014-12-19 11:20:52 +00:00
|
|
|
{
|
|
|
|
int i;
|
|
|
|
u32 pgsr0;
|
2016-10-27 14:47:07 +00:00
|
|
|
u32 init_flag = PHY_PIR_INIT;
|
|
|
|
u32 done_flag = PHY_PGSR0_IDONE;
|
2014-12-19 11:20:52 +00:00
|
|
|
int timeout = 50000; /* 50 msec is long enough */
|
2017-10-22 15:19:36 +00:00
|
|
|
#ifdef DEBUG
|
2014-12-19 11:20:52 +00:00
|
|
|
ulong start = get_timer(0);
|
|
|
|
#endif
|
|
|
|
|
|
|
|
for (i = 0; i < ARRAY_SIZE(init_sequence); i++) {
|
|
|
|
init_flag |= init_sequence[i].init_flag;
|
|
|
|
done_flag |= init_sequence[i].done_flag;
|
|
|
|
}
|
|
|
|
|
2016-10-27 14:47:07 +00:00
|
|
|
writel(init_flag, phy_base + PHY_PIR);
|
2014-12-19 11:20:52 +00:00
|
|
|
|
|
|
|
do {
|
|
|
|
if (--timeout < 0) {
|
2017-10-22 15:19:36 +00:00
|
|
|
pr_err("timeout during DDR training\n");
|
2015-12-16 01:50:26 +00:00
|
|
|
return -ETIMEDOUT;
|
2014-12-19 11:20:52 +00:00
|
|
|
}
|
|
|
|
udelay(1);
|
2016-10-27 14:47:07 +00:00
|
|
|
pgsr0 = readl(phy_base + PHY_PGSR0);
|
2014-12-19 11:20:52 +00:00
|
|
|
} while ((pgsr0 & done_flag) != done_flag);
|
|
|
|
|
|
|
|
for (i = 0; i < ARRAY_SIZE(init_sequence); i++) {
|
|
|
|
if (pgsr0 & init_sequence[i].err_flag) {
|
2017-10-22 15:19:36 +00:00
|
|
|
pr_err("%s failed\n", init_sequence[i].description);
|
2015-12-16 01:50:26 +00:00
|
|
|
return -EIO;
|
2014-12-19 11:20:52 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-10-22 15:19:36 +00:00
|
|
|
#ifdef DEBUG
|
|
|
|
pr_debug("DDR training: elapsed time %ld msec\n", get_timer(start));
|
2014-12-19 11:20:52 +00:00
|
|
|
#endif
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|