[PATCH] cmd: ti: Add DDRSS ECC test command
Neha Malcom Francis
n-francis at ti.com
Wed Oct 1 10:20:45 CEST 2025
From: Georgi Vlaev <g-vlaev at ti.com>
Introduce a new version of the Keystone-II "ddr" command for testing the
inline ECC support in the DDRSS bridge available on K3 devices. The ECC
hardware support in K3's DDRSS and the test method differ substantially
from what we support in the K2 variant of the command. The name of the
new command is "ddrss" and it presently supports only single controller
testing.
The ECC test procedure follows these steps:
1) Flush and disable the data cache.
2) Disable the protected ECC Rx range.
3) Flip a bit in the address.
4) Restore the range to original.
5) Read the modified value (corrected).
6) Re-enable the data cache.
This will cause the 1-bit ECC error count to increase while the read
will return the corrected value.
The K3 version of the command extends the syntax for the "ecc_err"
argument by also introducing an argument for range which specifies which
range (0, 1, 2) the address is located in.
Signed-off-by: Georgi Vlaev <g-vlaev at ti.com>
Signed-off-by: Santhosh Kumar K <s-k6 at ti.com>
[n-francis at ti.com: Add J7 and multiple-region support, simplify logic]
Signed-off-by: Neha Malcom Francis <n-francis at ti.com>
---
Test logs on J784S4-EVM (after modifying for single controller with ECC enabled)
https://gist.github.com/nehamalcom/80437234ddd2e22007dec3d1c37dcd6a
cmd/ti/Kconfig | 7 ++
cmd/ti/Makefile | 1 +
cmd/ti/ddrss.c | 243 ++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 251 insertions(+)
create mode 100644 cmd/ti/ddrss.c
diff --git a/cmd/ti/Kconfig b/cmd/ti/Kconfig
index 9442c9993c1..0d6c86bad3e 100644
--- a/cmd/ti/Kconfig
+++ b/cmd/ti/Kconfig
@@ -8,6 +8,13 @@ config CMD_DDR3
supports memory verification, memory comapre and ecc
verification if supported.
+config CMD_DDRSS
+ bool "command for verifying DDRSS Inline ECC features"
+ help
+ Support for testing DDRSS on TI platforms. This command supports
+ memory verification, memory compare and inline ECC verification
+ if supported.
+
config CMD_PD
bool "command for verifying power domains"
depends on TI_POWER_DOMAIN
diff --git a/cmd/ti/Makefile b/cmd/ti/Makefile
index 5f9c64f598a..d0555e7edf6 100644
--- a/cmd/ti/Makefile
+++ b/cmd/ti/Makefile
@@ -2,4 +2,5 @@
# Copyright (C) 2017 Texas Instruments Incorporated - https://www.ti.com/
obj-$(CONFIG_CMD_DDR3) += ddr3.o
+obj-$(CONFIG_CMD_DDRSS) += ddrss.o
obj-$(CONFIG_CMD_PD) += pd.o
diff --git a/cmd/ti/ddrss.c b/cmd/ti/ddrss.c
new file mode 100644
index 00000000000..d1481547938
--- /dev/null
+++ b/cmd/ti/ddrss.c
@@ -0,0 +1,243 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * DDRSS: DDR 1-bit inline ECC test command
+ *
+ * Copyright (C) 2025 Texas Instruments Incorporated - http://www.ti.com/
+ */
+
+#include <asm/io.h>
+#include <asm/cache.h>
+#include <asm/global_data.h>
+#include <command.h>
+#include <cpu_func.h>
+#include <linux/bitops.h>
+
+DECLARE_GLOBAL_DATA_PTR;
+
+#define K3_DDRSS_MAX_ECC_REGIONS 3
+
+#if (IS_ENABLED(CONFIG_SOC_K3_J784S4) ||\
+ IS_ENABLED(CONFIG_SOC_K3_J721S2) ||\
+ IS_ENABLED(CONFIG_SOC_K3_J7200) ||\
+ IS_ENABLED(CONFIG_SOC_K3_J721E))
+#define DDRSS_BASE 0x2980000
+#else
+#define DDRSS_BASE 0x0f300000
+#endif
+
+#define DDRSS_V2A_CTL_REG 0x0020
+#define DDRSS_V2A_INT_RAW_REG 0x00a0
+#define DDRSS_V2A_INT_STAT_REG 0x00a4
+#define DDRSS_V2A_INT_ECC1BERR BIT(3)
+#define DDRSS_V2A_INT_ECC2BERR BIT(4)
+#define DDRSS_V2A_INT_ECCM1BERR BIT(5)
+#define DDRSS_ECC_CTRL_REG 0x0120
+#define DDRSS_ECC_CTRL_REG_ECC_EN BIT(0)
+#define DDRSS_ECC_CTRL_REG_RMW_EN BIT(1)
+#define DDRSS_ECC_CTRL_REG_ECC_CK BIT(2)
+#define DDRSS_ECC_CTRL_REG_WR_ALLOC BIT(4)
+#define DDRSS_ECC_R0_STR_ADDR_REG 0x0130
+#define DDRSS_ECC_Rx_STR_ADDR_REG(x) (0x0130 + ((x) * 8))
+#define DDRSS_ECC_Rx_END_ADDR_REG(x) (0x0134 + ((x) * 8))
+#define DDRSS_ECC_1B_ERR_CNT_REG 0x0150
+#define DDRSS_ECC_1B_ERR_THRSH_REG 0x0154
+#define DDRSS_ECC_1B_ERR_ADR_REG 0x0158
+#define DDRSS_ECC_1B_ERR_MSK_LOG_REG 0x015c
+
+static inline u32 ddrss_read(u32 reg)
+{
+ return readl((unsigned long)(DDRSS_BASE + reg));
+}
+
+static inline void ddrss_write(u32 value, u32 reg)
+{
+ writel(value, (unsigned long)(DDRSS_BASE + reg));
+}
+
+/* ddrss_check_ecc_status()
+ * Report the ECC state after test. Check/clear the interrupt
+ * status register, dump the ECC err counters and ECC error offset.
+ */
+static void ddrss_check_ecc_status(void)
+{
+ u32 ecc_1b_err_cnt, v2a_int_raw, ecc_1b_err_msk;
+ phys_addr_t ecc_1b_err_adr;
+
+ v2a_int_raw = ddrss_read(DDRSS_V2A_INT_RAW_REG);
+
+ /* 1-bit correctable */
+ if (v2a_int_raw & DDRSS_V2A_INT_ECC1BERR) {
+ puts("\tECC test: DDR ECC 1-bit error\n");
+
+ /* Dump the 1-bit counter and reset it, as we want a
+ * new interrupt to be generated when above the error
+ * threshold
+ */
+ ecc_1b_err_cnt = ddrss_read(DDRSS_ECC_1B_ERR_CNT_REG);
+ if (ecc_1b_err_cnt) {
+ printf("\tECC test: 1-bit ECC err count: %u\n",
+ ecc_1b_err_cnt & 0xffff);
+ ddrss_write(1, DDRSS_ECC_1B_ERR_CNT_REG);
+ }
+
+ /* ECC fault addresses are also recorded in a 2-word deep
+ * FIFO. Calculate and report the 8-byte range of the error
+ */
+ ecc_1b_err_adr = ddrss_read(DDRSS_ECC_1B_ERR_ADR_REG);
+ ecc_1b_err_msk = ddrss_read(DDRSS_ECC_1B_ERR_MSK_LOG_REG);
+ if (ecc_1b_err_msk) {
+ if ((IS_ENABLED(CONFIG_SOC_K3_AM642)) ||
+ (IS_ENABLED(CONFIG_SOC_K3_AM625))) {
+ /* AM64/AM62:
+ * The address of the ecc error is 16-byte aligned.
+ * Each bit in 4 bit mask represents 8 bytes ECC quanta
+ * that has the 1-bit error
+ */
+ ecc_1b_err_msk &= 0xf;
+ ecc_1b_err_adr <<= 4;
+ ecc_1b_err_adr += (fls(ecc_1b_err_msk) - 1) * 8;
+ } else {
+ /* AM62A/AM62P:
+ * The address of the ecc error is 32-byte aligned.
+ * Each bit in 8 bit mask represents 8 bytes ECC quanta
+ * that has the 1-bit error
+ */
+ ecc_1b_err_msk &= 0xff;
+ ecc_1b_err_adr <<= 5;
+ ecc_1b_err_adr += (fls(ecc_1b_err_msk) - 1) * 8;
+ }
+
+ printf("\tECC test: 1-bit error in [0x%llx:0x%llx]\n",
+ ecc_1b_err_adr, ecc_1b_err_adr + 8);
+ /* Pop the top of the addr/mask FIFOs */
+ ddrss_write(1, DDRSS_ECC_1B_ERR_ADR_REG);
+ ddrss_write(1, DDRSS_ECC_1B_ERR_MSK_LOG_REG);
+ }
+ ddrss_write(DDRSS_V2A_INT_ECC1BERR, DDRSS_V2A_INT_STAT_REG);
+ }
+
+ /* 2-bit uncorrectable */
+ if (v2a_int_raw & DDRSS_V2A_INT_ECC2BERR) {
+ puts("\tECC test: DDR ECC 2-bit error\n");
+ ddrss_write(DDRSS_V2A_INT_ECC2BERR, DDRSS_V2A_INT_STAT_REG);
+ }
+
+ /* multiple 1-bit errors (uncorrectable) in multiple words */
+ if (v2a_int_raw & DDRSS_V2A_INT_ECCM1BERR) {
+ puts("\tECC test: DDR ECC multi 1-bit errors\n");
+ ddrss_write(DDRSS_V2A_INT_ECCM1BERR, DDRSS_V2A_INT_STAT_REG);
+ }
+}
+
+/* ddrss_memory_ecc_err()
+ * Simulate an ECC error - change a 32b word at address in an ECC enabled
+ * range. This removes the tested address from the ECC checks, changes a
+ * word, and then restores the ECC range as configured by k3_ddrss in R5 SPL.
+ */
+static int ddrss_memory_ecc_err(u64 addr, u64 ecc_err, int range)
+{
+ u64 ecc_start_addr, ecc_end_addr, ecc_temp_addr;
+ u64 val1, val2, val3;
+
+ /* Flush and disable dcache */
+ flush_dcache_all();
+ dcache_disable();
+
+ /* Setup a threshold for 1-bit errors to generate interrupt */
+ ddrss_write(1, DDRSS_ECC_1B_ERR_THRSH_REG);
+
+ puts("Testing DDR ECC:\n");
+ /* Get the Rx range configuration */
+ ecc_start_addr = ddrss_read(DDRSS_ECC_Rx_STR_ADDR_REG(range));
+ ecc_end_addr = ddrss_read(DDRSS_ECC_Rx_END_ADDR_REG(range));
+
+ /* Calculate the end of the Rx ECC region up to the tested address */
+ ecc_temp_addr = (addr - gd->ram_base) >> 16;
+
+ puts("\tECC test: Disabling DDR ECC ...\n");
+ /* Disable entire region */
+ ddrss_write(ecc_start_addr, DDRSS_ECC_Rx_END_ADDR_REG(range));
+ ddrss_write(ecc_end_addr, DDRSS_ECC_Rx_STR_ADDR_REG(range));
+
+ /* Inject error in the address */
+ val1 = readl((unsigned long long)addr);
+ val2 = val1 ^ ecc_err;
+ writel(val2, (unsigned long long)addr);
+ val3 = readl((unsigned long long)addr);
+
+ /* Re-enable the ECC checks for the R0 region */
+ ddrss_write(ecc_end_addr, DDRSS_ECC_Rx_END_ADDR_REG(range));
+ ddrss_write(ecc_start_addr, DDRSS_ECC_Rx_STR_ADDR_REG(range));
+ /* Make sure the ECC range is restored before doing anything else */
+ mb();
+
+ printf("\tECC test: addr 0x%llx, read data 0x%llx, written data 0x%llx, err pattern: 0x%llx, read after write data 0x%llx\n",
+ addr, val1, val2, ecc_err, val3);
+
+ puts("\tECC test: Enabled DDR ECC ...\n");
+ /* Read again from the address. This creates an ECC 1-bit error
+ * condition, and returns the corrected value
+ */
+ val1 = readl((unsigned long long)addr);
+ printf("\tECC test: addr 0x%llx, read data 0x%llx\n", addr, val1);
+
+ /* Set threshold for 1-bit errors to 0 to disable the interrupt */
+ ddrss_write(0, DDRSS_ECC_1B_ERR_THRSH_REG);
+ /* Report the ECC status */
+ ddrss_check_ecc_status();
+
+ dcache_enable();
+
+ return 0;
+}
+
+/* ddrss_is_ecc_enabled()
+ * Report if ECC is enabled.
+ */
+static int ddrss_is_ecc_enabled(void)
+{
+ u32 ecc_ctrl = ddrss_read(DDRSS_ECC_CTRL_REG);
+
+ /* Assume ECC is enabled only if all bits set by k3_ddrss are set */
+ return (ecc_ctrl & (DDRSS_ECC_CTRL_REG_ECC_EN |
+ DDRSS_ECC_CTRL_REG_RMW_EN |
+ DDRSS_ECC_CTRL_REG_WR_ALLOC |
+ DDRSS_ECC_CTRL_REG_ECC_CK));
+}
+
+static int do_ddrss_test(struct cmd_tbl *cmdtp, int flag, int argc,
+ char *const argv[])
+{
+ u64 start_addr, ecc_err, x;
+
+ if (!(argc == 5 && (strncmp(argv[1], "ecc_err", 8) == 0)))
+ return cmd_usage(cmdtp);
+
+ if (!ddrss_is_ecc_enabled()) {
+ puts("ECC not enabled. Please enable ECC any try again\n");
+ return CMD_RET_FAILURE;
+ }
+
+ start_addr = simple_strtoul(argv[2], NULL, 16);
+ ecc_err = simple_strtoul(argv[3], NULL, 16);
+ x = simple_strtoul(argv[4], NULL, 16);
+
+ if (!((start_addr >= gd->bd->bi_dram[0].start &&
+ (start_addr <= (gd->bd->bi_dram[0].start + gd->bd->bi_dram[0].size - 1))) ||
+ (start_addr >= gd->bd->bi_dram[1].start &&
+ (start_addr <= (gd->bd->bi_dram[1].start + gd->bd->bi_dram[1].size - 1))))) {
+ puts("Address is not in the DDR range\n");
+ return CMD_RET_FAILURE;
+ }
+
+ ddrss_memory_ecc_err(start_addr, ecc_err, x);
+ return 0;
+}
+
+U_BOOT_CMD(ddrss, 5, 1, do_ddrss_test,
+ "DDRSS test",
+ "ecc_err <addr in hex> <bit_err in hex> <range 0/1/2> - generate bit errors\n"
+ " in DDR data at <addr>, the command will read a 32-bit data\n"
+ " from <addr>, and write (data ^ bit_err) back to <addr>\n"
+ " in range 0, 1, or 2 (if default full region ECC is enabled, choose 0)\n"
+);
--
2.34.1
More information about the U-Boot
mailing list