zynqmp: spl: support DRAM ECC initialization
authorJorge Ramirez-Ortiz <jorge@foundries.io>
Sun, 13 Jun 2021 18:55:53 +0000 (20:55 +0200)
committerMichal Simek <michal.simek@xilinx.com>
Wed, 23 Jun 2021 07:48:35 +0000 (09:48 +0200)
Use the ZDMA channel 0 to initialize the DRAM banks. This avoid
spurious ECC errors that can occur when accessing unitialized memory.

The feature is enabled by setting the option
CONFIG_SPL_ZYNQMP_DRAM_ECC_INIT and providing the following data:

 SPL_ZYNQMP_DRAM_BANK1_BASE: start of memory to initialize
 SPL_ZYNQMP_DRAM_BANK1_LEN : len of memory to initialize (hex)
 SPL_ZYNQMP_DRAM_BANK2_BASE: start of memory to initialize
 SPL_ZYNQMP_DRAM_BANK2_LEN : len of memory to initialize (hex)

Setting SPL_ZYNQMP_DRAM_BANK_LEN to 0 takes no action.

Signed-off-by: Jorge Ramirez-Ortiz <jorge@foundries.io>
Signed-off-by: Michal Simek <michal.simek@xilinx.com>
arch/arm/mach-zynqmp/Kconfig
arch/arm/mach-zynqmp/Makefile
arch/arm/mach-zynqmp/ecc_spl_init.c [new file with mode: 0644]
arch/arm/mach-zynqmp/include/mach/ecc_spl_init.h [new file with mode: 0644]
arch/arm/mach-zynqmp/include/mach/hardware.h
arch/arm/mach-zynqmp/spl.c

index f1301f6..39144d6 100644 (file)
@@ -92,6 +92,41 @@ config ZYNQMP_NO_DDR
          This option configures MMU with no DDR to avoid speculative
          access to DDR memory where DDR is not present.
 
          This option configures MMU with no DDR to avoid speculative
          access to DDR memory where DDR is not present.
 
+config SPL_ZYNQMP_DRAM_ECC_INIT
+       bool "Initialize DRAM ECC"
+       depends on SPL
+       help
+         This option initializes all memory to 0xdeadbeef. Must be set if your
+         memory is of ECC type.
+
+config SPL_ZYNQMP_DRAM_BANK1_BASE
+       depends on SPL_ZYNQMP_DRAM_ECC_INIT
+       hex "DRAM Bank1 address"
+       default 0x00000000
+       help
+         Start address of DRAM ECC bank1
+
+config SPL_ZYNQMP_DRAM_BANK1_LEN
+       depends on SPL_ZYNQMP_DRAM_ECC_INIT
+       hex "DRAM Bank1 size"
+       default 0x80000000
+       help
+         Size in bytes of the DRAM ECC bank1
+
+config SPL_ZYNQMP_DRAM_BANK2_BASE
+       depends on SPL_ZYNQMP_DRAM_ECC_INIT
+       hex "DRAM Bank2 address"
+       default 0x800000000
+       help
+         Start address of DRAM ECC bank2
+
+config SPL_ZYNQMP_DRAM_BANK2_LEN
+       depends on SPL_ZYNQMP_DRAM_ECC_INIT
+       hex "DRAM Bank2 size"
+       default 0x0
+       help
+         Size in bytes of the DRAM ECC bank2. A null size takes no action.
+
 config SYS_MALLOC_F_LEN
        default 0x600
 
 config SYS_MALLOC_F_LEN
        default 0x600
 
index 8a3b074..eb6c511 100644 (file)
@@ -7,4 +7,5 @@ obj-y   += clk.o
 obj-y  += cpu.o
 obj-$(CONFIG_MP)       += mp.o
 obj-$(CONFIG_SPL_BUILD) += spl.o handoff.o
 obj-y  += cpu.o
 obj-$(CONFIG_MP)       += mp.o
 obj-$(CONFIG_SPL_BUILD) += spl.o handoff.o
+obj-$(CONFIG_SPL_ZYNQMP_DRAM_ECC_INIT) += ecc_spl_init.o
 obj-$(CONFIG_ZYNQMP_PSU_INIT_ENABLED)  += psu_spl_init.o
 obj-$(CONFIG_ZYNQMP_PSU_INIT_ENABLED)  += psu_spl_init.o
diff --git a/arch/arm/mach-zynqmp/ecc_spl_init.c b/arch/arm/mach-zynqmp/ecc_spl_init.c
new file mode 100644 (file)
index 0000000..f547d8e
--- /dev/null
@@ -0,0 +1,163 @@
+// SPDX-License-Identifier: MIT
+/*
+ *  Copyright(c) 2015 - 2020 Xilinx, Inc.
+ *
+ *  Jorge Ramirez-Ortiz <jorge@foundries.io>
+ */
+
+#include <common.h>
+#include <cpu_func.h>
+#include <asm/arch/hardware.h>
+#include <asm/arch/ecc_spl_init.h>
+#include <asm/io.h>
+#include <linux/delay.h>
+
+#define ZDMA_TRANSFER_MAX_LEN          (0x3FFFFFFFU - 7U)
+#define ZDMA_CH_STATUS                 ((ADMA_CH0_BASEADDR) + 0x0000011CU)
+#define ZDMA_CH_STATUS_STATE_MASK      0x00000003U
+#define ZDMA_CH_STATUS_STATE_DONE      0x00000000U
+#define ZDMA_CH_STATUS_STATE_ERR       0x00000003U
+#define ZDMA_CH_CTRL0                  ((ADMA_CH0_BASEADDR) + 0x00000110U)
+#define ZDMA_CH_CTRL0_POINT_TYPE_MASK  (u32)0x00000040U
+#define ZDMA_CH_CTRL0_POINT_TYPE_NORMAL        (u32)0x00000000U
+#define ZDMA_CH_CTRL0_MODE_MASK                (u32)0x00000030U
+#define ZDMA_CH_CTRL0_MODE_WR_ONLY     (u32)0x00000010U
+#define ZDMA_CH_CTRL0_TOTAL_BYTE_COUNT ((ADMA_CH0_BASEADDR) + 0x00000188U)
+#define ZDMA_CH_WR_ONLY_WORD0          ((ADMA_CH0_BASEADDR) + 0x00000148U)
+#define ZDMA_CH_WR_ONLY_WORD1          ((ADMA_CH0_BASEADDR) + 0x0000014CU)
+#define ZDMA_CH_WR_ONLY_WORD2          ((ADMA_CH0_BASEADDR) + 0x00000150U)
+#define ZDMA_CH_WR_ONLY_WORD3          ((ADMA_CH0_BASEADDR) + 0x00000154U)
+#define ZDMA_CH_DST_DSCR_WORD0         ((ADMA_CH0_BASEADDR) + 0x00000138U)
+#define ZDMA_CH_DST_DSCR_WORD0_LSB_MASK        0xFFFFFFFFU
+#define ZDMA_CH_DST_DSCR_WORD1         ((ADMA_CH0_BASEADDR) + 0x0000013CU)
+#define ZDMA_CH_DST_DSCR_WORD1_MSB_MASK        0x0001FFFFU
+#define ZDMA_CH_SRC_DSCR_WORD2         ((ADMA_CH0_BASEADDR) + 0x00000130U)
+#define ZDMA_CH_DST_DSCR_WORD2         ((ADMA_CH0_BASEADDR) + 0x00000140U)
+#define ZDMA_CH_CTRL2                  ((ADMA_CH0_BASEADDR) + 0x00000200U)
+#define ZDMA_CH_CTRL2_EN_MASK          0x00000001U
+#define ZDMA_CH_ISR                    ((ADMA_CH0_BASEADDR) + 0x00000100U)
+#define ZDMA_CH_ISR_DMA_DONE_MASK      0x00000400U
+#define ECC_INIT_VAL_WORD              0xDEADBEEFU
+
+#define ZDMA_IDLE_TIMEOUT_USEC         1000000
+#define ZDMA_DONE_TIMEOUT_USEC         5000000
+
+static void ecc_zdma_restore(void)
+{
+       /* Restore reset values for the DMA registers used */
+       writel(ZDMA_CH_CTRL0, 0x00000080U);
+       writel(ZDMA_CH_WR_ONLY_WORD0, 0x00000000U);
+       writel(ZDMA_CH_WR_ONLY_WORD1, 0x00000000U);
+       writel(ZDMA_CH_WR_ONLY_WORD2, 0x00000000U);
+       writel(ZDMA_CH_WR_ONLY_WORD3, 0x00000000U);
+       writel(ZDMA_CH_DST_DSCR_WORD0, 0x00000000U);
+       writel(ZDMA_CH_DST_DSCR_WORD1, 0x00000000U);
+       writel(ZDMA_CH_SRC_DSCR_WORD2, 0x00000000U);
+       writel(ZDMA_CH_DST_DSCR_WORD2, 0x00000000U);
+       writel(ZDMA_CH_CTRL0_TOTAL_BYTE_COUNT, 0x00000000U);
+}
+
+static void ecc_dram_bank_init(u64 addr, u64 len)
+{
+       bool retry = true;
+       u32 timeout;
+       u64 bytes;
+       u32 size;
+       u64 src;
+       u32 reg;
+
+       if (!len)
+               return;
+retry:
+       bytes = len;
+       src = addr;
+       ecc_zdma_restore();
+       while (bytes > 0) {
+               size = bytes > ZDMA_TRANSFER_MAX_LEN ?
+                       ZDMA_TRANSFER_MAX_LEN : (u32)bytes;
+
+               /* Wait until the DMA is in idle state */
+               timeout = ZDMA_IDLE_TIMEOUT_USEC;
+               do {
+                       udelay(1);
+                       reg = readl(ZDMA_CH_STATUS);
+                       reg &= ZDMA_CH_STATUS_STATE_MASK;
+                       if (!timeout--) {
+                               puts("error, ECC DMA failed to idle\n");
+                               goto done;
+                       }
+
+               } while ((reg != ZDMA_CH_STATUS_STATE_DONE) &&
+                       (reg != ZDMA_CH_STATUS_STATE_ERR));
+
+               /* Enable Simple (Write Only) Mode */
+               reg = readl(ZDMA_CH_CTRL0);
+               reg &= (ZDMA_CH_CTRL0_POINT_TYPE_MASK |
+                       ZDMA_CH_CTRL0_MODE_MASK);
+               reg |= (ZDMA_CH_CTRL0_POINT_TYPE_NORMAL |
+                       ZDMA_CH_CTRL0_MODE_WR_ONLY);
+               writel(reg, ZDMA_CH_CTRL0);
+
+               /* Fill in the data to be written */
+               writel(ECC_INIT_VAL_WORD, ZDMA_CH_WR_ONLY_WORD0);
+               writel(ECC_INIT_VAL_WORD, ZDMA_CH_WR_ONLY_WORD1);
+               writel(ECC_INIT_VAL_WORD, ZDMA_CH_WR_ONLY_WORD2);
+               writel(ECC_INIT_VAL_WORD, ZDMA_CH_WR_ONLY_WORD3);
+
+               /* Write Destination Address */
+               writel((u32)(src & ZDMA_CH_DST_DSCR_WORD0_LSB_MASK),
+                      ZDMA_CH_DST_DSCR_WORD0);
+               writel((u32)((src >> 32) & ZDMA_CH_DST_DSCR_WORD1_MSB_MASK),
+                      ZDMA_CH_DST_DSCR_WORD1);
+
+               /* Size to be Transferred. Recommended to set both src and dest sizes */
+               writel(size, ZDMA_CH_SRC_DSCR_WORD2);
+               writel(size, ZDMA_CH_DST_DSCR_WORD2);
+
+               /* DMA Enable */
+               reg = readl(ZDMA_CH_CTRL2);
+               reg |= ZDMA_CH_CTRL2_EN_MASK;
+               writel(reg, ZDMA_CH_CTRL2);
+
+               /* Check the status of the transfer by polling on DMA Done */
+               timeout = ZDMA_DONE_TIMEOUT_USEC;
+               do {
+                       udelay(1);
+                       reg = readl(ZDMA_CH_ISR);
+                       reg &= ZDMA_CH_ISR_DMA_DONE_MASK;
+                       if (!timeout--) {
+                               puts("error, ECC DMA timeout\n");
+                               goto done;
+                       }
+               } while (reg != ZDMA_CH_ISR_DMA_DONE_MASK);
+
+               /* Clear DMA status */
+               reg = readl(ZDMA_CH_ISR);
+               reg |= ZDMA_CH_ISR_DMA_DONE_MASK;
+               writel(ZDMA_CH_ISR_DMA_DONE_MASK, ZDMA_CH_ISR);
+
+               /* Read the channel status for errors */
+               reg = readl(ZDMA_CH_STATUS);
+               if (reg == ZDMA_CH_STATUS_STATE_ERR) {
+                       if (retry) {
+                               retry = false;
+                               goto retry;
+                       }
+                       puts("error, ECC DMA error\n");
+                       break;
+               }
+
+               bytes -= size;
+               src += size;
+       }
+done:
+       ecc_zdma_restore();
+}
+
+void zynqmp_ecc_init(void)
+{
+       ecc_dram_bank_init(CONFIG_SPL_ZYNQMP_DRAM_BANK1_BASE,
+                          CONFIG_SPL_ZYNQMP_DRAM_BANK1_LEN);
+       ecc_dram_bank_init(CONFIG_SPL_ZYNQMP_DRAM_BANK2_BASE,
+                          CONFIG_SPL_ZYNQMP_DRAM_BANK2_LEN);
+}
diff --git a/arch/arm/mach-zynqmp/include/mach/ecc_spl_init.h b/arch/arm/mach-zynqmp/include/mach/ecc_spl_init.h
new file mode 100644 (file)
index 0000000..b4b6fcf
--- /dev/null
@@ -0,0 +1,13 @@
+/* SPDX-License-Identifier: MIT */
+/*
+ *  Copyright(c) 2015 - 2020 Xilinx, Inc.
+ *
+ *  Jorge Ramirez-Ortiz <jorge@foundries.io>
+ */
+
+#ifndef __ARCH_ZYNQMP_ECC_INIT_H
+#define __ARCH_ZYNQMP_ECC_INIT_H
+
+void zynqmp_ecc_init(void);
+
+#endif
index a798aa0..3776499 100644 (file)
@@ -24,6 +24,8 @@
                                                            + 0x00000114)
 #define ZYNQMP_PS_SYSMON_ANALOG_BUS_VAL 0x00003210
 
                                                            + 0x00000114)
 #define ZYNQMP_PS_SYSMON_ANALOG_BUS_VAL 0x00003210
 
+#define ADMA_CH0_BASEADDR      0xFFA80000
+
 #define PS_MODE0       BIT(0)
 #define PS_MODE1       BIT(1)
 #define PS_MODE2       BIT(2)
 #define PS_MODE0       BIT(0)
 #define PS_MODE1       BIT(1)
 #define PS_MODE2       BIT(2)
index 88386b2..8fcae2c 100644 (file)
@@ -15,6 +15,7 @@
 #include <asm/io.h>
 #include <asm/spl.h>
 #include <asm/arch/hardware.h>
 #include <asm/io.h>
 #include <asm/spl.h>
 #include <asm/arch/hardware.h>
+#include <asm/arch/ecc_spl_init.h>
 #include <asm/arch/psu_init_gpl.h>
 #include <asm/arch/sys_proto.h>
 
 #include <asm/arch/psu_init_gpl.h>
 #include <asm/arch/sys_proto.h>
 
@@ -22,6 +23,9 @@ void board_init_f(ulong dummy)
 {
        board_early_init_f();
        board_early_init_r();
 {
        board_early_init_f();
        board_early_init_r();
+#ifdef CONFIG_SPL_ZYNQMP_DRAM_ECC_INIT
+       zynqmp_ecc_init();
+#endif
 }
 
 static void ps_mode_reset(ulong mode)
 }
 
 static void ps_mode_reset(ulong mode)