sunxi: dram: Re-introduce the impedance calibration ond ODT

The DRAM controller allows to configure impedance either by using the
calibration against an external high precision 240 ohm resistor, or
by skipping the calibration and loading pre-defined data. The DRAM
controller register guide is available here:

    http://linux-sunxi.org/A10_DRAM_Controller_Register_Guide#SDR_ZQCR0

The new code supports both of the impedance configuration modes:
   - If the higher bits of the 'zq' parameter in the 'dram_para' struct
     are zero, then the lowest 8 bits are used as the ZPROG value, where
     two divisors encoded in lower and higher 4 bits. One divisor is
     used for calibrating the termination impedance, and another is used
     for the output impedance.
   - If bits 27:8 in the 'zq' parameters are non-zero, then they are
     used as the pre-defined ZDATA value instead of performing the ZQ
     calibration.

Two lowest bits in the 'odt_en' parameter enable ODT for the DQ and DQS
lines individually. Enabling ODT for both DQ and DQS means that the
'odt_en' parameter needs to be set to 3.

Signed-off-by: Siarhei Siamashka <siarhei.siamashka@gmail.com>
Acked-by: Ian Campbell <ijc@hellion.org.uk>
Signed-off-by: Hans de Goede <hdegoede@redhat.com>
diff --git a/arch/arm/cpu/armv7/sunxi/dram.c b/arch/arm/cpu/armv7/sunxi/dram.c
index 4a72480..f87cbeb 100644
--- a/arch/arm/cpu/armv7/sunxi/dram.c
+++ b/arch/arm/cpu/armv7/sunxi/dram.c
@@ -445,6 +445,60 @@
 	await_bits_clear(&dram->ccr, DRAM_CCR_INIT);
 }
 
+/*
+ * Perform impedance calibration on the DRAM controller side of the wire.
+ */
+static void mctl_set_impedance(u32 zq, u32 odt_en)
+{
+	struct sunxi_dram_reg *dram = (struct sunxi_dram_reg *)SUNXI_DRAMC_BASE;
+	u32 reg_val;
+	u32 zprog = zq & 0xFF, zdata = (zq >> 8) & 0xFFFFF;
+
+#ifndef CONFIG_SUN7I
+	/* Appears that some kind of automatically initiated default
+	 * ZQ calibration is already in progress at this point on sun4i/sun5i
+	 * hardware, but not on sun7i. So it is reasonable to wait for its
+	 * completion before doing anything else. */
+	await_bits_set(&dram->zqsr, DRAM_ZQSR_ZDONE);
+#endif
+
+	/* ZQ calibration is not really useful unless ODT is enabled */
+	if (!odt_en)
+		return;
+
+#ifdef CONFIG_SUN7I
+	/* Enabling ODT in SDR_IOCR on sun7i hardware results in a deadlock
+	 * unless bit 24 is set in SDR_ZQCR1. Not much is known about the
+	 * SDR_ZQCR1 register, but there are hints indicating that it might
+	 * be related to periodic impedance re-calibration. This particular
+	 * magic value is borrowed from the Allwinner boot0 bootloader, and
+	 * using it helps to avoid troubles */
+	writel((1 << 24) | (1 << 1), &dram->zqcr1);
+#endif
+
+	/* Needed at least for sun5i, because it does not self clear there */
+	clrbits_le32(&dram->zqcr0, DRAM_ZQCR0_ZCAL);
+
+	if (zdata) {
+		/* Set the user supplied impedance data */
+		reg_val = DRAM_ZQCR0_ZDEN | zdata;
+		writel(reg_val, &dram->zqcr0);
+		/* no need to wait, this takes effect immediately */
+	} else {
+		/* Do the calibration using the external resistor */
+		reg_val = DRAM_ZQCR0_ZCAL | DRAM_ZQCR0_IMP_DIV(zprog);
+		writel(reg_val, &dram->zqcr0);
+		/* Wait for the new impedance configuration to settle */
+		await_bits_set(&dram->zqsr, DRAM_ZQSR_ZDONE);
+	}
+
+	/* Needed at least for sun5i, because it does not self clear there */
+	clrbits_le32(&dram->zqcr0, DRAM_ZQCR0_ZCAL);
+
+	/* Set I/O configure register */
+	writel(DRAM_IOCR_ODT_EN(odt_en), &dram->iocr);
+}
+
 unsigned long dramc_init(struct dram_para *para)
 {
 	struct sunxi_dram_reg *dram = (struct sunxi_dram_reg *)SUNXI_DRAMC_BASE;
@@ -505,6 +559,8 @@
 
 	dramc_clock_output_en(1);
 
+	mctl_set_impedance(para->zq, para->odt_en);
+
 	mctl_set_cke_delay();
 
 	mctl_ddr3_reset();
diff --git a/arch/arm/include/asm/arch-sunxi/dram.h b/arch/arm/include/asm/arch-sunxi/dram.h
index 67fbfad..4433eeb 100644
--- a/arch/arm/include/asm/arch-sunxi/dram.h
+++ b/arch/arm/include/asm/arch-sunxi/dram.h
@@ -159,6 +159,10 @@
 
 #define DRAM_ZQCR0_IMP_DIV(n) (((n) & 0xff) << 20)
 #define DRAM_ZQCR0_IMP_DIV_MASK DRAM_ZQCR0_IMP_DIV(0xff)
+#define DRAM_ZQCR0_ZCAL (1 << 31) /* Starts ZQ calibration when set to 1 */
+#define DRAM_ZQCR0_ZDEN (1 << 28) /* Uses ZDATA instead of doing calibration */
+
+#define DRAM_ZQSR_ZDONE (1 << 31) /* ZQ calibration completion flag */
 
 #define DRAM_IOCR_ODT_EN(n) ((((n) & 0x3) << 30) | ((n) & 0x3) << 0)
 #define DRAM_IOCR_ODT_EN_MASK DRAM_IOCR_ODT_EN(0x3)