/**************************************************************************** ; ; MODULE cmd_dramtests.c ; ; DESCRIPTION This module implements the dramtests UBoot command, which ; conducts BAU and BCE DRAM testing ; ; ; THIS PROGRAM AND THE INFORMATION CONTAINED HEREIN IS THE PROPERTY OF ; ATTO TECHNOLOGY, INC. AND SHALL NOT BE REPRODUCED, COPIED, OR USED IN ; WHOLE OR IN PART OTHER THAN AS PROVIDED FOR IN THE LICENSE AGREEMENT ; PURSUANT TO WHICH IT WAS FURNISHED. ; ; COPYRIGHT (c) ATTO TECHNOLOGY, INC. 2016 ; ALL RIGHTS RESERVED. ; *****************************************************************************/ #include #include #include #include #include #include #include #include #include static void dram_test_setup(void * base_addr, uint16_t test_pattern, uint32_t start_addr, uint32_t size, uint32_t seed0, uint32_t seed1, uint32_t seed2, uint32_t seed3); static int dram_tests(uint32_t * test_engine_base_addresses[], BAU_DRAM_TEST bau_dram_tests[], uint32_t bau_dram_test_sizes[], int post_type); static void absolute_printf(const char *pcFmt, ...); static void dram_test_fail_hang_bridge(void); uint32_t bau_test_engine_addresses[] = {BCE_DDR4_TE_BASE, BAU_DDR4_TE_0_BASE, BAU_DDR4_TE_1_BASE, NULL}; uint32_t bau_test_sizes[] = {BCE_DRAM_TEST_SIZE, BAU_DRAM_TEST_SIZE, BAU_DRAM_TEST_SIZE}; BAU_DRAM_TEST bau_tests[] = { BAU_DRAM_TEST_RANDOM, BAU_DRAM_TEST_A5_NONINVERTING, BAU_DRAM_TEST_5A_NONINVERTING, BAU_DRAM_TEST_NONINVERTING, BAU_DRAM_TEST_NULL}; BAU_DRAM_TEST bau_tests_extended[] = { BAU_DRAM_TEST_MARCHC1, BAU_DRAM_TEST_MARCHC2, BAU_DRAM_TEST_MARCHC3, BAU_DRAM_TEST_MARCHC4, BAU_DRAM_TEST_MARCHC5, BAU_DRAM_TEST_MARCHC6, BAU_DRAM_TEST_MARCHC7, BAU_DRAM_TEST_RANDOM, BAU_DRAM_TEST_NONINVERTING, BAU_DRAM_TEST_NULL}; /******************************************************************************* ; ; FUNCTION dram_tests ; ; RESPONSIBILITY Run DRAM tests on the given engine's memory space. ; ; PARAMETERS Name I/O Description ; test_engine_base_addresses ; I array of DRAM test engine base addresses ; bau_dram_tests I array of DRAM test engine descriptors ; ; RETURNS ATTO_SUCCESS on pass ATTO_FAILURE on failure ; ; NOTES ARRAYS PASSED IN MUST HAVE NULL ENDING. ; *******************************************************************************/ static int dram_tests(uint32_t * test_engine_base_addresses[], BAU_DRAM_TEST bau_dram_tests[], uint32_t bau_dram_test_sizes[], int post_type) { /* Made up value tuned during testing. */ #define ENGINE_STATUS_TIMEOUT 0x001fffff /* Only one test for now but the array will allow for expansion. */ BAU_DRAM_TEST current_test; uint8_t stop_test, engines_busy; uint8_t test_status = ATTO_SUCCESS; uint8_t march_c_next_state = MC_STATE_NONE; uint8_t march_c_verify = 0; uint16_t pattern, x16; uint32_t loop_count, x, num_tests, num_engines, idx, test_num; volatile void * base_addr; vuint16_t * addr_16; vuint32_t * addr_32, bau_ddr_0_ecc_status, bau_ddr_1_ecc_status; vuint32_t bce_ddr_ecc_status; int num_byte_lanes; /* This is just to avoid doing the same thing I've done 100 times which is * not executing fpgabr 1 on the command line and wonder why data aborts * are happening. */ reset_deassert_bridges_handoff(); #if defined(CONFIG_ATTO_DISABLE_ECC) vuint32_t * bau_ddr_0_cfg_reg = (vuint32_t *)BAU_DDR_0_CFG_REG; vuint32_t * bau_ddr_1_cfg_reg = (vuint32_t *)BAU_DDR_1_CFG_REG; vuint32_t * bce_ddr_ecccntrl_reg = (vuint32_t *)BCE_ECC_ECCCNTRL_1_REG; #endif /* defined(CONFIG_ATTO_DISABLE_ECC) */ /* ECC register declarations */ vuint32_t * bau_ddr_0_ctrl_reg = (vuint32_t *)BAU_DDR_0_CTRL_REG; vuint32_t * bau_ddr_0_status_reg = (vuint32_t *)BAU_DDR_0_STATUS_REG; vuint32_t * bau_ddr_0_error_addr_reg = (vuint32_t *)BAU_DDR_0_ERROR_ADDR_REG; vuint32_t * bau_ddr_1_ctrl_reg = (vuint32_t *)BAU_DDR_1_CTRL_REG; vuint32_t * bau_ddr_1_status_reg = (vuint32_t *)BAU_DDR_1_STATUS_REG; vuint32_t * bau_ddr_1_error_addr_reg = (vuint32_t *)BAU_DDR_1_ERROR_ADDR_REG; vuint32_t * bce_ddr_status_reg = (vuint32_t *)BCE_ECC_STATUS_REG; vuint32_t * bce_inten_reg = (vuint32_t *)BCE_ECC_INTEN_REG; vuint32_t * bce_ddr_sbe_error_addr_reg = (vuint32_t *)BCE_ECC_DBE_ERRADDR_REG; vuint32_t * bce_ddr_dbe_error_addr_reg = (vuint32_t *)BCE_ECC_SBE_ERRADDR_REG; /* figure out the size of the passed in arrays */ idx = 0; while(test_engine_base_addresses[idx] != NULL) { ++idx; } num_engines = idx; idx = 0; while(bau_dram_tests[idx].size != 0) { ++idx; } num_tests = idx; /* Clear outstanding ECC errors and turn off interrupts * (even though they probably aren't enabled) */ x = *bau_ddr_0_ctrl_reg; bau_ddr_0_ecc_status = x; x &= ~BAU_ECC_CTRL_INT_ENABLE; x |= BAU_ECC_CTRL_CLEAR; *bau_ddr_0_ctrl_reg = x; x = *bau_ddr_1_ctrl_reg; bau_ddr_1_ecc_status = x; x &= ~BAU_ECC_CTRL_INT_ENABLE; x |= BAU_ECC_CTRL_CLEAR; *bau_ddr_1_ctrl_reg = x; x = *bce_ddr_status_reg; x |= BCE_ECC_CLEAR_MASK; *bce_ddr_status_reg = x; x = *bce_inten_reg; bce_ddr_ecc_status = x; x &= ~BCE_ECC_INTEN_MASK; *bce_inten_reg = x; #if defined(CONFIG_ATTO_DISABLE_ECC) x = *bau_ddr_0_cfg_reg; x &= ~BAU_DDR_CFG_ECCENABLE_MASK; *bau_ddr_0_cfg_reg = x; x = *bau_ddr_1_cfg_reg; x &= ~BAU_DDR_CFG_ECCENABLE_MASK; *bau_ddr_1_cfg_reg = x; x = *bce_ddr_ecccntrl_reg; x &= ~BCE_ECC_ECCCNTRL_1_ECC_EN_MASK; *bce_ddr_ecccntrl_reg = x; #endif /* defined(CONFIG_ATTO_DISABLE_ECC) */ if (post_type == RUN_EXTENDED_POST) { absolute_printf("Starting Extended POST DRAM Tests\n"); } else { absolute_printf("Starting DRAM Tests\n"); } for(test_num = 0; (test_num < num_tests) && (test_status == ATTO_SUCCESS); ++test_num) { current_test = bau_dram_tests[test_num]; stop_test = 0; if((current_test.pattern & BAU_DDR_TE_TP_MARCHC) == BAU_DDR_TE_TP_MARCHC) { march_c_next_state = MC_STATE_MARCHUP; pattern = BAU_DDR_TE_TP_NONINVERTING; } else { march_c_next_state = MC_STATE_NONE; pattern = current_test.pattern; } /* set up the registers to get ready for the test */ for(idx = 0; idx < num_engines; ++idx) { dram_test_setup(test_engine_base_addresses[idx], pattern, current_test.base_addr, bau_dram_test_sizes[idx], current_test.seed0, current_test.seed1, current_test.seed2, current_test.seed3); } absolute_printf("DRAM Test #%d", test_num + 1); while(stop_test == 0) { /* START YOUR ENGINES */ for(idx = 0; idx < num_engines; ++idx) { base_addr = test_engine_base_addresses[idx]; addr_16 = (uint16_t *)(base_addr + BAU_DDR_TE_CTRL_OFFSET); x16 = *addr_16; /* Clear everything EXCEPT the test pattern mask. */ x16 &= BAU_DDR_TE_TP_MASK; if(march_c_verify) { /* If we are verifying then it only makes sense to read. */ x16 |= BAU_DDR_TE_CTRL_RUN_MASK | BAU_DDR_TE_CTRL_TEST_MASK | BAU_DDR_TE_READONLY; *addr_16 = x16; march_c_verify = 0; } else { x16 |= BAU_DDR_TE_CTRL_RUN_MASK | BAU_DDR_TE_CTRL_TEST_MASK; *addr_16 = x16; } } loop_count = 0; do { /* In English this says: sleep for a little bit and then if any * of our engines are still busy continue checking, otherwise * break out and move on from this loop. */ udelay(100); engines_busy = 0; for(idx = 0; idx < num_engines; ++idx) { base_addr = test_engine_base_addresses[idx]; addr_16 = (uint16_t *)(base_addr + BAU_DDR_TE_CTRL_OFFSET); if((*addr_16 & BAU_DDR_TE_CTRL_RUN_MASK) == BAU_DDR_TE_CTRL_RUN_MASK) { engines_busy = 1; } } if(!(loop_count % 0x3fff)) { absolute_printf("."); } ++loop_count; } while((loop_count < ENGINE_STATUS_TIMEOUT) && engines_busy); if(loop_count >= ENGINE_STATUS_TIMEOUT) { absolute_printf("DRAM test timed out\n"); } for(idx = 0; idx < num_engines; ++idx) { base_addr = test_engine_base_addresses[idx]; addr_16 = (uint16_t *)(base_addr + BAU_DDR_TE_CTRL_OFFSET); if((*addr_16 & BAU_DDR_TE_STS_FAIL) == BAU_DDR_TE_STS_FAIL) { test_status = ATTO_FAILURE; stop_test = 1; addr_32 = (uint32_t *)(base_addr + BAU_DDR_TE_ERRORADDR_OFFSET); if(base_addr == (uint32_t *)BCE_DDR4_TE_BASE) { absolute_printf("\nBCE DTE FAILED:"); } else if(base_addr == (uint32_t *)BAU_DDR4_TE_0_BASE) { absolute_printf("\nBAU DTE 0 FAILED:"); } else if(base_addr == (uint32_t *)BAU_DDR4_TE_1_BASE) { absolute_printf("\nBAU DTE 1 FAILED:"); } absolute_printf(" FAILED: DRAM Error\n"); absolute_printf("Error Address: 0x%08x\n", *addr_32); addr_32 = (uint32_t *)(base_addr + BAU_DDR_TE_ERRORDWORD0_OFFSET); if(addr_16 == (uint16_t *)BCE_DDR4_TE_BASE) { num_byte_lanes = BCE_DDR_TE_NUMBYTELANES; } else { num_byte_lanes = BAU_DDR_TE_NUMBYTELANES; } for(idx = 0; idx < num_byte_lanes; ++idx) { absolute_printf(" %08x\n", *addr_32); addr_32 = (uint32_t *)((uint32_t)addr_32 + sizeof(uint32_t)); } absolute_printf("\n"); } } x = *bau_ddr_0_status_reg; if( ((x & BAU_ECC_STATUS_SBE) == BAU_ECC_STATUS_SBE) || ((x & BAU_ECC_STATUS_DBE) == BAU_ECC_STATUS_DBE) || ((x & BAU_ECC_STATUS_DRP) == BAU_ECC_STATUS_DRP)) { absolute_printf("\nBAU DTE 0 FAILED:"); if((x & BAU_ECC_STATUS_SBE) == BAU_ECC_STATUS_SBE) { absolute_printf(" Single Bit Error\n"); } else if((x & BAU_ECC_STATUS_DBE) == BAU_ECC_STATUS_DBE) { absolute_printf(" Double Bit Error\n"); } else if((x & BAU_ECC_STATUS_DRP) == BAU_ECC_STATUS_DRP) { absolute_printf(" Drop Error\n"); } absolute_printf(" ECC Check Status: 0x%08x\n", *bau_ddr_0_status_reg); absolute_printf(" ECC Error Address: 0x%08x\n", *bau_ddr_0_error_addr_reg); test_status = ATTO_FAILURE; stop_test = 1; } x = *bau_ddr_1_status_reg; if( ((x & BAU_ECC_STATUS_SBE) == BAU_ECC_STATUS_SBE) || ((x & BAU_ECC_STATUS_DBE) == BAU_ECC_STATUS_DBE) || ((x & BAU_ECC_STATUS_DRP) == BAU_ECC_STATUS_DRP)) { absolute_printf("\nBAU DTE 1 FAILED:"); if((x & BAU_ECC_STATUS_SBE) == BAU_ECC_STATUS_SBE) { absolute_printf(" Single Bit Error\n"); } else if((x & BAU_ECC_STATUS_DBE) == BAU_ECC_STATUS_DBE) { absolute_printf(" Double Bit Error\n"); } else if((x & BAU_ECC_STATUS_DRP) == BAU_ECC_STATUS_DRP) { absolute_printf(" Drop Error\n"); } absolute_printf(" ECC Error Address: 0x%08x\n", *bau_ddr_1_error_addr_reg); absolute_printf(" ECC Check Status: 0x%08x\n", *bau_ddr_1_status_reg); test_status = ATTO_FAILURE; stop_test = 1; } x = *bce_ddr_status_reg; if( ((x & BCE_ECC_SBE_MASK) == BCE_ECC_SBE_MASK) || ((x & BCE_ECC_DBE_MASK) == BCE_ECC_DBE_MASK)) { absolute_printf("\nBCE DTE FAILED:"); if((x & BCE_ECC_SBE_MASK) == BCE_ECC_SBE_MASK) { absolute_printf(" Single Bit Error\n"); absolute_printf(" ECC Error Address: 0x%08x\n", *bce_ddr_sbe_error_addr_reg); } if((x & BCE_ECC_DBE_MASK) == BCE_ECC_DBE_MASK) { absolute_printf(" Double Bit Error\n"); absolute_printf(" ECC Error Address: 0x%08x\n", *bce_ddr_dbe_error_addr_reg); } absolute_printf(" ECC Check Status: 0x%08x\n", *bce_ddr_status_reg); test_status = ATTO_FAILURE; stop_test = 1; } /* if there was a failure, hang indefinitely */ if (test_status == ATTO_FAILURE) { dram_test_fail_hang_bridge(); } if( (march_c_next_state == MC_STATE_NONE) && (test_status == ATTO_SUCCESS)) { /* Will fall in here if we finished the march c test or if * we passed any other test. */ stop_test = 1; absolute_printf(" PASSED\n", test_num + 1); } if(test_status == ATTO_SUCCESS) { switch(march_c_next_state) { case MC_STATE_NONE: /* not in a march c test */ break; case MC_STATE_MARCHUP: /* write original data pattern to entire memory using * incrementing addresses * * test is already set up for this case so no need to call * the setup function */ for(idx = 0; idx < num_engines; ++idx) { base_addr = test_engine_base_addresses[idx]; addr_16 = (uint16_t *)(base_addr + BAU_DDR_TE_CTRL_OFFSET); x16 = *addr_16; x16 &= ~BAU_DDR_TE_TP_MASK; x16 |= BAU_DDR_TE_TP_MARCHUP; *addr_16 = x16; } march_c_next_state = MC_STATE_MARCHUP_INV; break; case MC_STATE_MARCHUP_INV: /* write inverted pattern to entire memory using * incrementing addresses */ for(idx = 0; idx < num_engines; ++idx) { dram_test_setup(test_engine_base_addresses[idx], BAU_DDR_TE_TP_MARCHUP, current_test.base_addr, bau_dram_test_sizes[idx], ~current_test.seed0, ~current_test.seed1, ~current_test.seed2, ~current_test.seed3); } march_c_next_state = MC_STATE_VERIFY_MARCHUP; break; case MC_STATE_VERIFY_MARCHUP: /* verify the entire memory is set to the original data * pattern */ for(idx = 0; idx < num_engines; ++idx) { dram_test_setup(test_engine_base_addresses[idx], BAU_DDR_TE_TP_NONINVERTING, current_test.base_addr, bau_dram_test_sizes[idx], current_test.seed0, current_test.seed1, current_test.seed2, current_test.seed3); } march_c_verify = 1; march_c_next_state = MC_STATE_MARCHDOWN; break; case MC_STATE_MARCHDOWN: /* write original data pattern to entire memory using * decrementing addresses */ for(idx = 0; idx < num_engines; ++idx) { dram_test_setup(test_engine_base_addresses[idx], BAU_DDR_TE_TP_MARCHDOWN, /* start test and end of mem */ current_test.base_addr + bau_dram_test_sizes[idx] - *(vuint8_t *)((vuint32_t)test_engine_base_addresses[idx] + BAU_DDR_TE_XFERSIZE_OFFSET), bau_dram_test_sizes[idx], current_test.seed0, current_test.seed1, current_test.seed2, current_test.seed3); } march_c_next_state = MC_STATE_MARCHDOWN_INV; break; case MC_STATE_MARCHDOWN_INV: /* write inverted pattern to entire memory using * decrementing addresses */ for(idx = 0; idx < num_engines; ++idx) { dram_test_setup(test_engine_base_addresses[idx], BAU_DDR_TE_TP_MARCHDOWN, /* start test and end of mem */ current_test.base_addr + bau_dram_test_sizes[idx] - *(uint8_t *)((vuint32_t)test_engine_base_addresses[idx] + BAU_DDR_TE_XFERSIZE_OFFSET), bau_dram_test_sizes[idx], ~current_test.seed0, ~current_test.seed1, ~current_test.seed2, ~current_test.seed3); } march_c_next_state = MC_STATE_VERIFY_MARCHDOWN; break; case MC_STATE_VERIFY_MARCHDOWN: /* verify the entire memory is set to the original data * pattern */ for(idx = 0; idx < num_engines; ++idx) { dram_test_setup(test_engine_base_addresses[idx], BAU_DDR_TE_TP_NONINVERTING, current_test.base_addr, bau_dram_test_sizes[idx], current_test.seed0, current_test.seed1, current_test.seed2, current_test.seed3); } march_c_verify = 1; march_c_next_state = MC_STATE_NONE; break; default: break; } } } } /* restore whatever status the interrupts were in before the test */ x = *bau_ddr_0_ctrl_reg; x |= (BAU_ECC_CTRL_INT_ENABLE & bau_ddr_0_ecc_status); *bau_ddr_0_ctrl_reg = x; x = *bau_ddr_1_ctrl_reg; x |= (BAU_ECC_CTRL_INT_ENABLE & bau_ddr_1_ecc_status); *bau_ddr_1_ctrl_reg = x; x = *bce_inten_reg; x |= (BCE_ECC_INTEN_MASK & bce_ddr_ecc_status); *bce_inten_reg = x; #if defined(CONFIG_ATTO_DISABLE_ECC) x = *bau_ddr_0_cfg_reg; x |= BAU_DDR_CFG_ECCENABLE_MASK; *bau_ddr_0_cfg_reg = x; x = *bau_ddr_1_cfg_reg; x |= BAU_DDR_CFG_ECCENABLE_MASK; *bau_ddr_1_cfg_reg = x; x = *bce_ddr_ecccntrl_reg; x |= BCE_ECC_ECCCNTRL_1_ECC_EN_MASK; *bce_ddr_ecccntrl_reg = x; #endif /* defined(CONFIG_ATTO_DISABLE_ECC) */ return test_status; } /******************************************************************************* ; ; FUNCTION dram_test_setup ; ; RESPONSIBILITY Sets up a dram test engine at base_addr to run a test. ; ; PARAMETERS Name I/O Description ; base_addr I test engine base address ; test_pattern I type of test to set up for ; start_addr I address for test to start at ; size I amount of memory to test in Bytes ; seed0 I ; seed1 I ; seed2 I ; seed3 I ; ; RETURNS void ; ; NOTES ; *******************************************************************************/ static void dram_test_setup(void * base_addr, uint16_t test_pattern, uint32_t start_addr, uint32_t size, uint32_t seed0, uint32_t seed1, uint32_t seed2, uint32_t seed3) { vuint16_t ui16; vuint16_t * addr_16; vuint32_t * addr_32; if((size < BAU_DRAM_MIN_TEST_SIZE) || (size > BAU_DRAM_SIZE)) { absolute_printf( "Invalid DRAM Test Size 0x08%x - setting to minimum\n", size); size = BAU_DRAM_MIN_TEST_SIZE; } addr_16 = (uint16_t *)(base_addr + BAU_DDR_TE_CTRL_OFFSET); ui16 = *addr_16; ui16 &= ~BAU_DDR_TE_TP_MASK; ui16 |= test_pattern; *addr_16 = ui16; addr_32 = (uint32_t *)(base_addr + BAU_DDR_TE_STARTADDR_OFFSET); *addr_32 = start_addr; addr_32 = (uint32_t *)(base_addr + BAU_DDR_TE_NUMBYTES_OFFSET); *addr_32 = size; addr_32 = (uint32_t *)(base_addr + BAU_DDR_TE_SEED0_OFFSET); *addr_32 = seed0; addr_32 = (uint32_t *)(base_addr + BAU_DDR_TE_SEED1_OFFSET); *addr_32 = seed1; addr_32 = (uint32_t *)(base_addr + BAU_DDR_TE_SEED2_OFFSET); *addr_32 = seed2; addr_32 = (uint32_t *)(base_addr + BAU_DDR_TE_SEED3_OFFSET); *addr_32 = seed3; } /******************************************************************************* ; ; FUNCTION dram_test_fail_hang_bridge ; ; RESPONSIBILITY Hangs the bridge in the event of a failure ; ; PARAMETERS Name I/O Description ; ; RETURNS void ; ; NOTES ; *******************************************************************************/ static void dram_test_fail_hang_bridge(void) { /* If we experience a failure during the DTE tests, simply * hang the bridge in an infinite loop */ while (1) { } } int do_dram_tests(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[]) { /* Run the full March C DRAM tests if extended post, othewise run smaller, * quicker set of tests. We use software handoff register 6 to see if * extended post should be run - non-zero means run extended post */ uint32_t * handoff_reg_6 = (uint32_t *)ARRIA10_HPS_SYSMGR_ROM_ISWHANDOFF_6; if (*handoff_reg_6) dram_tests((uint32_t **)bau_test_engine_addresses, bau_tests_extended, bau_test_sizes, RUN_EXTENDED_POST); else dram_tests((uint32_t **)bau_test_engine_addresses, bau_tests, bau_test_sizes, RUN_NORMAL_POST); return 0; } /******************************************************************************* ; ; FUNCTION absolute_printf ; ; RESPONSIBILITY Sends stuff to the console no matter what. ; ; PARAMETERS Name I/O Description ; s I string to print out ; ; RETURNS void ; ; NOTES ; *******************************************************************************/ static void absolute_printf(const char *pcFmt, ...) { va_list ap; char buffer[255]; va_start(ap, pcFmt); vsnprintf (buffer, 255, pcFmt, ap); va_end(ap); fprintf(stdout, buffer); } U_BOOT_CMD( dramtests, 2, 1, do_dram_tests, "", "" );