Files

252 lines
8.0 KiB
C

// 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 64b 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_ddr4_ecc_inject(struct cmd_tbl *cmdtp, int flag, int argc,
char *const argv[])
{
u64 start_addr, ecc_err, range;
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);
range = simple_strtoul(argv[4], NULL, 16);
if (range < 0 || range > 2) {
puts("Range must be 0, 1 or 2\n");
return CMD_RET_FAILURE;
}
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, range);
return 0;
}
U_BOOT_CMD(ddr, 5, 1, do_ddr4_ecc_inject,
"DDRSS Inline ECC Injection Tool",
"ecc_err <addr in hex> <bit_err in hex> <range 0/1/2>\n"
" Generate bit errors in DDR data at <addr>, the command will read\n"
" a 64-bit data from <addr>, and write (data ^ bit_err) back to\n"
" <addr>. The ECC 1-bit error count is reported in the given range\n"
" 0, 1, or 2 (if default full region ECC is enabled, choose 0).\n"
" Trying to inject a multiple-bit error or multiple single-bit\n"
" errors will result in a synchronous abort and is expected.\n"
);