All of lore.kernel.org
 help / color / mirror / Atom feed
From: Marek Vasut <marex@denx.de>
To: u-boot@lists.denx.de
Subject: [U-Boot] [PATCH 1/2] arm: imx6: Add DDR3 calibration code for MX6 Q/D/DL
Date: Tue, 22 Dec 2015 02:52:02 +0100	[thread overview]
Message-ID: <201512220252.02451.marex@denx.de> (raw)
In-Reply-To: <56770222.1050108@nelint.com>

On Sunday, December 20, 2015 at 08:31:46 PM, Eric Nelson wrote:
> Hi Marek,
> 
> On 12/16/2015 07:40 AM, Marek Vasut wrote:
> > Add DDR3 calibration code for i.MX6Q, i.MX6D and i.MX6DL. This code
> > fine-tunes the behavior of the MMDC controller in order to improve
> > the signal integrity and memory stability.
> 
> It would be good to have a reference to AN4467 in the commit message,
> since that document is the best reference to understanding this code:
> 
> 	http://cache.freescale.com/files/32bit/doc/app_note/AN4467.pdf

OK

> > Signed-off-by: Marek Vasut <marex@denx.de>
> > Cc: Stefano Babic <sbabic@denx.de>
> > ---
> > 
> >  arch/arm/cpu/armv7/mx6/ddr.c            | 559
> >  ++++++++++++++++++++++++++++++++
> >  arch/arm/include/asm/arch-mx6/mx6-ddr.h |   5 +
> >  2 files changed, 564 insertions(+)
> 
> Note that there's another implementation of this in the barebox
> project:
> 
> http://git.pengutronix.de/?p=barebox.git;a=blob;f=arch/arm/mach-imx/imx6-mm
> dc.c;h=146df573e4eeed7132e93899b4b539e842bb6ba2;hb=HEAD

Yeah, I sent them a patch for it too ;-)

> > diff --git a/arch/arm/cpu/armv7/mx6/ddr.c b/arch/arm/cpu/armv7/mx6/ddr.c
> > index 6b039e4..194411f 100644
> > --- a/arch/arm/cpu/armv7/mx6/ddr.c
> > +++ b/arch/arm/cpu/armv7/mx6/ddr.c
> > @@ -13,6 +13,565 @@
> > 
> >  #include <asm/io.h>
> >  #include <asm/types.h>
> > 
> > +#if defined(CONFIG_MX6QDL) || defined(CONFIG_MX6Q) ||
> > defined(CONFIG_MX6D) +
> 
> Should this use the common wait_for_bit?
> 	http://lists.denx.de/pipermail/u-boot/2015-December/thread.html#237952
> 
> I'm not certain that the <ctrl-c> handling is appropriate when
> used here.

Yes, that's why I called it exactly the same, so once the patches above hit
mainline, I could just nuke this function and replace it with the common one.

> > +static int wait_for_bit(void *reg, const uint32_t mask, bool set)
> > +{
> > +	unsigned int timeout = 1000;
> > +	u32 val;
> > +
> > +	while (--timeout) {
> > +		val = readl(reg);
> > +		if (!set)
> > +			val = ~val;
> > +
> > +		if ((val & mask) == mask)
> > +			return 0;
> > +
> > +		udelay(1);
> > +	}
> > +
> > +	printf("%s: Timeout (reg=%p mask=%08x wait_set=%i)\n",
> > +	      __func__, reg, mask, set);
> > +	hang();	/* DRAM couldn't be calibrated, game over :-( */
> > +}
> > +
> > +static void reset_read_data_fifos(void)
> > +{
> > +	struct mmdc_p_regs *mmdc0 = (struct mmdc_p_regs *)MMDC_P0_BASE_ADDR;
> > +
> > +	/* Reset data FIFOs twice. */
> > +	setbits_le32(&mmdc0->mpdgctrl0, 1 << 31);
> > +	wait_for_bit(&mmdc0->mpdgctrl0, 1 << 31, 0);
> > +
> > +	setbits_le32(&mmdc0->mpdgctrl0, 1 << 31);
> > +	wait_for_bit(&mmdc0->mpdgctrl0, 1 << 31, 0);
> > +}
> > +
> 
> See comments below about chip-selects during calibration.
> 
> > +static void precharge_all(const bool cs0_enable, const bool cs1_enable)
> > +{
> > +	struct mmdc_p_regs *mmdc0 = (struct mmdc_p_regs *)MMDC_P0_BASE_ADDR;
> > +
> > +	/*
> > +	 * Issue the Precharge-All command to the DDR device for both
> > +	 * chip selects. Note, CON_REQ bit should also remain set. If
> > +	 * only using one chip select, then precharge only the desired
> > +	 * chip select.
> > +	 */
> 
> AN4467 says that a wait for tRP is needed here, though none of the
> other implementations explicitly do that.
> 
> The wait for CON_ACK seems like a good idea, though I wonder if
> the waits shouldn't be performed **before** the commands are issued.
> 
> I'd also like to see a named constant for MDSCR_CON_ACK

That'd be great. Do you know of some header where the MMDC register bits
are defined ? I am definitelly not gonna rewrite the the i.MX6 MMDC bits
from the datasheet by hand.

> , and since
> waiting for it is done in multiple places, perhaps wait_for_con_ack().

Implementing a function for the sake of just wrapping another function
seems wasteful to me.

> > +	if (cs0_enable) { /* CS0 */
> > +		writel(0x04008050, &mmdc0->mdscr);
> > +		wait_for_bit(&mmdc0->mdscr, 1 << 14, 1);
> > +	}
> > +
> > +	if (cs1_enable) { /* CS1 */
> > +		writel(0x04008058, &mmdc0->mdscr);
> > +		wait_for_bit(&mmdc0->mdscr, 1 << 14, 1);
> > +	}
> > +}
> > +
> > +static void force_delay_measurement(int bus_size)
> > +{
> > +	struct mmdc_p_regs *mmdc0 = (struct mmdc_p_regs *)MMDC_P0_BASE_ADDR;
> > +	struct mmdc_p_regs *mmdc1 = (struct mmdc_p_regs *)MMDC_P1_BASE_ADDR;
> > +
> > +	writel(0x800, &mmdc0->mpmur0);
> > +	if (bus_size == 0x2)
> > +		writel(0x800, &mmdc1->mpmur0);
> > +}
> > +
> > +static void modify_dg_result(u32 *reg_st0, u32 *reg_st1, u32 *reg_ctrl)
> > +{
> > +	u32 dg_tmp_val, dg_dl_abs_offset, dg_hc_del, val_ctrl;
> > +
> > +	/*
> > +	 * DQS gating absolute offset should be modified from reflecting
> > +	 * (HW_DG_LOWx + HW_DG_UPx)/2 to reflecting (HW_DG_UPx - 0x80)
> > +	 */
> > +
> > +	val_ctrl = readl(reg_ctrl);
> > +	val_ctrl &= 0xf0000000;
> > +
> 
> Though this matches the app note and other implementations, I think
> this would be easier to understand by shifting first and anding with
> 0x7ff.

I think it is better to stick with the appnote, so it's easy to map the
code and the appnote between one another.

> > +	dg_tmp_val = ((readl(reg_st0) & 0x07ff0000) >> 16) - 0xc0;
> 
> Especially since these calculations operate on the shifted values:
> > +	dg_dl_abs_offset = dg_tmp_val & 0x7f;
> > +	dg_hc_del = (dg_tmp_val & 0x780) << 1;
> > +
> > +	val_ctrl |= dg_dl_abs_offset + dg_hc_del;
> > +
> > +	dg_tmp_val = ((readl(reg_st1) & 0x07ff0000) >> 16) - 0xc0;
> > +	dg_dl_abs_offset = dg_tmp_val & 0x7f;
> > +	dg_hc_del = (dg_tmp_val & 0x780) << 1;
> > +
> > +	val_ctrl |= (dg_dl_abs_offset + dg_hc_del) << 16;
> > +
> > +	writel(val_ctrl, reg_ctrl);
> > +}
> > +
> 
> I'd recommend passing parameters of mx6_ddr_sysinfo (input) and
> mx6_mmdc_calibration (output) to this routine.

Why exactly ?

> > +int mmdc_do_write_level_calibration(void)
> > +{
> > +	struct mmdc_p_regs *mmdc0 = (struct mmdc_p_regs *)MMDC_P0_BASE_ADDR;
> > +	struct mmdc_p_regs *mmdc1 = (struct mmdc_p_regs *)MMDC_P1_BASE_ADDR;
> > +	u32 esdmisc_val, zq_val;
> > +	u32 errors = 0;
> > +	u32 ldectrl[4];
> 
> I believe the 4 here comes from this calculation:
> 
> 	ddr_mr1 = ((sysinfo->rtt_nom & 1) ? 1 : 0) << 2 |
> 	          ((sysinfo->rtt_nom & 2) ? 1 : 0) << 6;
> 
> > +	u32 ddr_mr1 = 0x4;
> > +
> 
> I'm not sure how this is useful (trying to proceed if things fail).
> 
> Have you seen this triggered?

What ? I see an assignment , what would trigger here ?

> > +	/*
> > +	 * Stash old values in case calibration fails,
> > +	 * we need to restore them
> > +	 */
> > +	ldectrl[0] = readl(&mmdc0->mpwldectrl0);
> > +	ldectrl[1] = readl(&mmdc0->mpwldectrl1);
> > +	ldectrl[2] = readl(&mmdc1->mpwldectrl0);
> > +	ldectrl[3] = readl(&mmdc1->mpwldectrl1);
> > +
> > +	/* disable DDR logic power down timer */
> > +	clrbits_le32(&mmdc0->mdpdc, 0xff00);
> > +
> 
> Either s/Adopt/Automatic/ or just get rid of Adopt...
> 
> This is a typo in the app note.

OK

> > +	/* disable Adopt power down timer */
> > +	setbits_le32(&mmdc0->mapsr, 0x1);
> > +
> > +	debug("Starting write leveling calibration.\n");
> > +
> > +	/*
> > +	 * 2. disable auto refresh and ZQ calibration
> > +	 * before proceeding with Write Leveling calibration
> > +	 */
> 
> Trusting the esdmisc value read from here seems like a bad idea.
> It would be clearer if the code below that restores things just
> (re)configures mdref directly.

Can you elaborate some more on why is it a bad idea?

> > +	esdmisc_val = readl(&mmdc0->mdref);
> > +	writel(0x0000C000, &mmdc0->mdref);
> > +	zq_val = readl(&mmdc0->mpzqhwctrl);
> 
> clrbits_le32?

The zq_val is used further in the code, so no.

> > +	writel(zq_val & ~0x3, &mmdc0->mpzqhwctrl);
> > +
> > +	/* 3. increase walat and ralat to maximum */
> 
> (3 << 16) | (7 << 6) would be clearer, since these are numeric
> values in tbe bitfields.
> 
> > +	setbits_le32(&mmdc0->mdmisc,
> > +		     (1 << 6) | (1 << 7) | (1 << 8) | (1 << 16) | (1 << 17));
> > +	setbits_le32(&mmdc1->mdmisc,
> > +		     (1 << 6) | (1 << 7) | (1 << 8) | (1 << 16) | (1 << 17));
> 
> I had a concern about this bit of code (setting RALAT and WALAT to
> maximum values). It seems odd that we calibrate with one set of delays
> and then choose a different set later.
> 
> Experimentally, I've found using min or max values seems to have
> no effect on the result.

OK

> > +	/*
> > +	 * 4 & 5. Configure the external DDR device to enter write-leveling
> > +	 * mode through Load Mode Register command.
> > +	 * Register setting:
> > +	 * Bits[31:16] MR1 value (0x0080 write leveling enable)
> > +	 * Bit[9] set WL_EN to enable MMDC DQS output
> > +	 * Bits[6:4] set CMD bits for Load Mode Register programming
> > +	 * Bits[2:0] set CMD_BA to 0x1 for DDR MR1 programming
> > +	 */
> 
> wait_for_con_ack() seems like a good idea here:

Why ?

> > +	writel(0x00808231, &mmdc0->mdscr);
> > +
> > +	/* 6. Activate automatic calibration by setting MPWLGCR[HW_WL_EN] */
> > +	writel(0x00000001, &mmdc0->mpwlgcr);
> > +
> > +	/*
> > +	 * 7. Upon completion of this process the MMDC de-asserts
> > +	 * the MPWLGCR[HW_WL_EN]
> > +	 */
> 
> Why 1 << 0?

It waits for the autocalibration to finish, see 6. above.

> > +	wait_for_bit(&mmdc0->mpwlgcr, 1 << 0, 0);
> > +
> > +	/*
> > +	 * 8. check for any errors: check both PHYs for x64 configuration,
> > +	 * if x32, check only PHY0
> > +	 */
> > +	if (readl(&mmdc0->mpwlgcr) & 0x00000F00)
> > +		errors |= 1;
> 
> This should be conditional on (sysinfo->dsize == 2) to
> 
> allow use with a 32-bit bus:

I believe mmdc1 will contain 0x0 << 8 if you use 32bit bus, right ?

> > +	if (readl(&mmdc1->mpwlgcr) & 0x00000F00)
> > +		errors |= 2;
> > +
> > +	debug("Ending write leveling calibration. Error mask: 0x%x\n", errors);
> > +
> 
> Where are you getting this test from?
> 
> I have code that tests each byte individually against a max
> value of 0x2f, but I'm having trouble finding the source of
> the test at the moment.

This one came I think from the freescale code from novena repo.

> > +	/* check to see if cal failed */
> > +	if ((readl(&mmdc0->mpwldectrl0) == 0x001F001F) &&
> > +	    (readl(&mmdc0->mpwldectrl1) == 0x001F001F) &&
> 
> Ditto (sysinfo->dsize == 2):
> > +	    (readl(&mmdc1->mpwldectrl0) == 0x001F001F) &&
> > +	    (readl(&mmdc1->mpwldectrl1) == 0x001F001F)) {
> > +		debug("Cal seems to have soft-failed due to memory not 
supporting
> > write leveling on all channels. Restoring original write leveling
> > values.\n"); +		writel(ldectrl[0], &mmdc0->mpwldectrl0);
> > +		writel(ldectrl[1], &mmdc0->mpwldectrl1);
> > +		writel(ldectrl[2], &mmdc1->mpwldectrl0);
> > +		writel(ldectrl[3], &mmdc1->mpwldectrl1);
> > +		errors |= 4;
> > +	}
> > +
> > +	/*
> > +	 * User should issue MRS command to exit write leveling mode
> > +	 * through Load Mode Register command
> > +	 * Register setting:
> > +	 * Bits[31:16] MR1 value "ddr_mr1" value from initialization
> > +	 * Bit[9] clear WL_EN to disable MMDC DQS output
> > +	 * Bits[6:4] set CMD bits for Load Mode Register programming
> > +	 * Bits[2:0] set CMD_BA to 0x1 for DDR MR1 programming
> > +	 */
> > +	writel((ddr_mr1 << 16) + 0x8031, &mmdc0->mdscr);
> > +
> 
> As mentioned earlier, you can just set mdref here, instead of
> using a saved value. The only assignment is currently this:
> 
> 	mmdc0->mdref = (0 << 14) |
> 		       (3 << 11);
> 
> > +	/* re-enable auto refresh and zq cal */
> > +	writel(esdmisc_val, &mmdc0->mdref);
> 
> I think you can just use clrbits32_le here and get rid of zq_val.
> 
> > +	writel(zq_val, &mmdc0->mpzqhwctrl);
> > +
> > +	debug("\tMMDC_MPWLDECTRL0 after write level cal: 0x%08X\n",
> > +	      readl(&mmdc0->mpwldectrl0));
> > +	debug("\tMMDC_MPWLDECTRL1 after write level cal: 0x%08X\n",
> > +	      readl(&mmdc0->mpwldectrl1));
> > +	debug("\tMMDC_MPWLDECTRL0 after write level cal: 0x%08X\n",
> > +	      readl(&mmdc1->mpwldectrl0));
> > +	debug("\tMMDC_MPWLDECTRL1 after write level cal: 0x%08X\n",
> > +	      readl(&mmdc1->mpwldectrl1));
> > +
> 
> Why the dummy reads below?
> 
> I'd like to see the values fed back in an output parameter:
> 	calib->p0_mpwldectrl0 = readl(&mmdc0->mpwldectrl0);
> 	calib->p0_mpwldectrl1 = readl(&mmdc0->mpwldectrl1);
> 
> Absent that, I don't think a dummy read is needed.

You do not need those values anymore, since they are already loaded into the 
hardware ... or why would you want them to be adjusted ?

> > +	/* We must force a readback of these values, to get them to stick */
> > +	readl(&mmdc0->mpwldectrl0);
> > +	readl(&mmdc0->mpwldectrl1);
> > +	readl(&mmdc1->mpwldectrl0);
> > +	readl(&mmdc1->mpwldectrl1);
> > +
> > +	/* enable DDR logic power down timer: */
> > +	setbits_le32(&mmdc0->mdpdc, 0x00005500);
> > +
> 
> Again, s/Adopt/Automatic/
> 
> > +	/* Enable Adopt power down timer: */
> > +	clrbits_le32(&mmdc0->mapsr, 0x1);
> > +
> > +	/* Clear CON_REQ */
> > +	writel(0, &mmdc0->mdscr);
> > +
> > +	return errors;
> > +}
> > +
> 
> This should also have parameters of mx6_ddr_sysinfo (input) and
> mx6_mmdc_calibration (output), at least for sysinfo->dsize

Would it be possible for you to send a subsequent patch(set)? I would like to 
have this code as a working base , since I tested this thoroughly. If I apply 
all of your changes, it would basically mean almost complete rewrite of the code 
and that would disallow me bisect possible bugs introduced by these changes.

[...]

  reply	other threads:[~2015-12-22  1:52 UTC|newest]

Thread overview: 47+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2015-12-16 14:40 [U-Boot] [PATCH 1/2] arm: imx6: Add DDR3 calibration code for MX6 Q/D/DL Marek Vasut
2015-12-16 14:40 ` [U-Boot] [PATCH 2/2] arm: imx6: Enable DDR calibration on Novena Marek Vasut
2015-12-20 19:46   ` Eric Nelson
     [not found]   ` <567702A6.9070107@cox.net>
2015-12-22  1:26     ` Marek Vasut
2015-12-22  8:30       ` Nikolay Dimitrov
2015-12-22 14:56         ` Marek Vasut
2015-12-16 15:00 ` [U-Boot] [PATCH 1/2] arm: imx6: Add DDR3 calibration code for MX6 Q/D/DL Eric Nelson
2015-12-16 15:33   ` Marek Vasut
2015-12-16 16:28     ` Eric Nelson
2015-12-16 16:50       ` Marek Vasut
2015-12-16 17:07         ` Eric Nelson
2015-12-16 17:11           ` Marek Vasut
2015-12-17 15:39   ` Tim Harvey
2015-12-17 15:48     ` Eric Nelson
2015-12-17 15:36 ` Tim Harvey
2015-12-17 15:40   ` Marek Vasut
2015-12-17 16:15     ` Tim Harvey
2015-12-17 16:17       ` Marek Vasut
2015-12-17 21:32 ` Nikolay Dimitrov
2015-12-17 22:11   ` Marek Vasut
2015-12-20 19:31 ` Eric Nelson
2015-12-22  1:52   ` Marek Vasut [this message]
2015-12-22 15:37     ` Eric Nelson
2016-01-14  2:10       ` Marek Vasut
2016-01-14  2:37         ` Eric Nelson
2016-01-14  2:50           ` Marek Vasut
2016-01-14  2:52             ` Eric Nelson
2016-01-14  3:06               ` Marek Vasut
2016-01-14 14:25                 ` Tim Harvey
2016-01-24 10:47                   ` Stefano Babic
2016-01-24 16:01                     ` Marek Vasut
2016-01-24 17:03                       ` Stefano Babic
2016-01-24 17:11                         ` Marek Vasut
2016-01-24 17:18                           ` Stefano Babic
2016-01-24 17:22                             ` Marek Vasut
2016-01-24 19:33                               ` Tom Rini
2016-01-24 22:07                                 ` Marek Vasut
2016-01-24 22:21                                   ` Tom Rini
2016-01-24 23:00                                     ` Marek Vasut
2016-01-24 23:30                                       ` Tom Rini
2016-01-24 23:55                                         ` Marek Vasut
2016-01-31 17:25                                         ` Marek Vasut
2016-06-02 13:20                                           ` Tim Harvey
2016-06-02 14:23                                             ` Stefano Babic
2016-06-02 15:25                                               ` Tim Harvey
2015-12-22  4:13   ` Tim Harvey
2015-12-22 14:47     ` Eric Nelson

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=201512220252.02451.marex@denx.de \
    --to=marex@denx.de \
    --cc=u-boot@lists.denx.de \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.