/******************************************************************************

  Copyright (C), 2010-2013, Texas Instrument.

 ******************************************************************************
  File Name     : Robust_DDR_ECC.c
  Version       : Initial Draft
  Author        : Brighton Feng
  Created       : 2012/11/2
  Last Modified :
  Description   : DDR ECC test example
  Function List :
  History       :
  1.Date        : August 15, 2013
    Author      : Brighton Feng
    Modification: Updated according to new usage notes about spurious errors

******************************************************************************/

#include <stdio.h>
#include <string.h>
#include <csl_edma3.h>
#include "K2_common.h"
#include "K2_DDR_init.h"
#include "K2_board_init.h"

/*----------------------------------------------*
 * module-wide global variables                 *
 *----------------------------------------------*/
CSL_Emif4fvRegs * DDR3ControlRegs;
 
#define DDR_ECC_TEST_BUF_SIZE_BYTES (32*1024)
Uint8 DDR_ECC_Test[DDR_ECC_TEST_BUF_SIZE_BYTES] __attribute__ ((aligned (DDR_ECC_TEST_BUF_SIZE_BYTES), section ("DDRECCData")));
Uint8 DDR_ECC_Test_no_cache[DDR_ECC_TEST_BUF_SIZE_BYTES] __attribute__ ((aligned (DDR_ECC_TEST_BUF_SIZE_BYTES), section ("DDRECC_no_cache_Data")));

/*----------------------------------------------*
 * routines' implementations                    *
 *----------------------------------------------*/
/*Setup EDMA PARAM for DDR ECC scrub.
DDR range from "uiStartAddr" with "uiTotalByteCnt" need be scrubbed,
EDMA should scrub "uiBlockSize_KB" by every trigger. (uiBlockSize_KB must less than 32)
Note: This method is only feasible for read ONLY data or code. For writable data,
there may be a race condition when another mater write to same address during scrubbing:
1, DMA read address A
2, another master write to address A
3, DMA write to address A
To avoid this race condition, EDMA scrubbing should only be setup for read-only range, 
such as, code space, or only enable this EDMA channel when DDR is idle*/
void DDR_ECC_scrub_EDMA_PARAM_set(Uint32 uiPARAM_num, Uint32 uiTcc, Uint32 uiLinkNum, 
	Uint32 uiStartAddr, Uint32 uiTotalByteCnt, Uint32 uiBlockSize_KB)
{
	if(0==uiTotalByteCnt)
		return;

	uiBlockSize_KB &= 0x7FFF;
	
	//intermediate chaining is uesed to achieve ABC SYNC.
	gpEDMA_CC_regs[0]->PARAMSET[uiPARAM_num].OPT= 
		CSL_EDMA3_OPT_MAKE(CSL_EDMA3_ITCCH_DIS, 
			CSL_EDMA3_TCCH_DIS, 
			CSL_EDMA3_ITCINT_DIS, 
			CSL_EDMA3_TCINT_EN,
			uiTcc,
			CSL_EDMA3_TCC_NORMAL,
			CSL_EDMA3_FIFOWIDTH_NONE, 
			CSL_EDMA3_STATIC_DIS, 
			CSL_EDMA3_SYNC_AB, 
			CSL_EDMA3_ADDRMODE_INCR, 
			CSL_EDMA3_ADDRMODE_INCR);
	gpEDMA_CC_regs[0]->PARAMSET[uiPARAM_num].SRC= uiStartAddr;  
	gpEDMA_CC_regs[0]->PARAMSET[uiPARAM_num].A_B_CNT= CSL_EDMA3_CNT_MAKE(1024, uiBlockSize_KB);
	gpEDMA_CC_regs[0]->PARAMSET[uiPARAM_num].DST= uiStartAddr;
	gpEDMA_CC_regs[0]->PARAMSET[uiPARAM_num].SRC_DST_BIDX= CSL_EDMA3_BIDX_MAKE(1024, 1024);
	gpEDMA_CC_regs[0]->PARAMSET[uiPARAM_num].LINK_BCNTRLD= CSL_EDMA3_LINKBCNTRLD_MAKE(uiLinkNum*32, uiBlockSize_KB);
	gpEDMA_CC_regs[0]->PARAMSET[uiPARAM_num].SRC_DST_CIDX= CSL_EDMA3_CIDX_MAKE(uiBlockSize_KB*1024, uiBlockSize_KB*1024);
	gpEDMA_CC_regs[0]->PARAMSET[uiPARAM_num].CCNT= uiTotalByteCnt/(uiBlockSize_KB*1024);
	
}

/*****************************************************************************
setup timer and EDMA to scrub DDR EDC periodically.
In every "scrubbing_period_ms", timer will trigger EDMA to scrub "uiBlockSize_KB" in DDR.
uiBlockSize_KB must be less than 32.
*****************************************************************************/
void DDR_EDC_scrubbing_setup(unsigned int scrubbing_period_ms, 
	Uint32 uiBlockSize_KB, DDR3_ECC_Config * ecc_cfg)
{
	int i;
	Uint32 uiRangeCnt;
	DDR3_ECC_Addr_Range addressRanges[3];
	Timer64_Config tmrCfg;

	if((NULL==ecc_cfg)||(0==uiBlockSize_KB)||(0==scrubbing_period_ms))
		return;

	uiRangeCnt= get_DDR_ECC_ranges(ecc_cfg, addressRanges, DDR3A_SIZE_BYTES);
	if(0==uiRangeCnt)
		return;

	tmrCfg.timer_num= 9;
	tmrCfg.timerMode= TIMER_PERIODIC_PULSE;
	/*timer period is in the unit of Main core clock/6*/	
	tmrCfg.period= (unsigned long long)gMain_Core_Speed_Hz*scrubbing_period_ms/6000; 	
	tmrCfg.pulseWidth= 3;
	Timer64_Init(&tmrCfg);

	/*Note: 36-bit DDR3 internal address need be translated into 32-bit EDMA virtul address.
	if the ECC range is larger than 2GB, SES MPAX need be reconfigured.*/
	DDR_ECC_scrub_EDMA_PARAM_set(CSL_EDMACC_0_TIMER_9_INTL, CSL_EDMACC_0_TIMER_9_INTL, 64+1,
		(Uint32)addressRanges[0].startAddr+0x80000000, (Uint32)addressRanges[0].byteCnt, uiBlockSize_KB);
	//init linking PARAM, PARAM 64~66 are used for this test
	for(i=0; i<uiRangeCnt; i++)
	{
		DDR_ECC_scrub_EDMA_PARAM_set(64+i, CSL_EDMACC_0_TIMER_9_INTL, 64+i+1,
			(Uint32)addressRanges[i].startAddr+0x80000000, (Uint32)addressRanges[i].byteCnt, uiBlockSize_KB);
	}
	//wrap back the last linking
	gpEDMA_CC_regs[0]->PARAMSET[64+uiRangeCnt-1].LINK_BCNTRLD= CSL_EDMA3_LINKBCNTRLD_MAKE(64*32, uiBlockSize_KB);

	//clear channel event
	gpEDMA_CC_regs[0]->TPCC_ECR= (1<<CSL_EDMACC_0_TIMER_9_INTL);

	//enable channel
	gpEDMA_CC_regs[0]->TPCC_EESR= (1<<CSL_EDMACC_0_TIMER_9_INTL);
	
}

/*DDR ECC test with CPU*/
void DDR_ECC_test_core()
{
	volatile Uint8 readValue;
	Uint32 uiOrignECC_CTL;

	puts("test DDR ECC in non-cachable range...");
	puts("disable Read-Modify-Write feature of DDR ECC.");
	DDR3ControlRegs->ECC_CTRL &= ~(DDR3_ECC_CTRL_REG_RMW_EN_MASK);
	delay_ms(1);

	printf("!!!write one byte 0x55 to address 0x%x in DDR ECC range ...\n",
		(Uint32)&DDR_ECC_Test_no_cache[1]);

	DDR_ECC_Test_no_cache[1]= 0x55;
	/*normally, exception is captured about 10~100 cycles after the
	access violation. Dummy operations added here to wait for it.*/
	dummy_wait(1000);	

	//reset memory content to 0
	EDMA_fill((unsigned int)DDR_ECC_Test_no_cache, 0, 128, EDMA_CC0_CH0);
	__asm__(" ISB");

	//printf("One bit ECC error count= %d\n", DDR3ControlRegs->ONE_BIT_ECC_ERR_CNT);
	//DDR3ControlRegs->ONE_BIT_ECC_ERR_CNT= 1; //decrease error count by unaligned write
	
	puts("\nEnable Read-Modify-Write feature of DDR ECC.");
	DDR3ControlRegs->ECC_CTRL |= DDR3_ECC_CTRL_REG_RMW_EN_MASK;
	delay_ms(1);

	printf("write one byte 0x55 to address 0x%x in DDR ECC range (no write ECC exception will happen).\n",
		(Uint32)&DDR_ECC_Test_no_cache[1]);

	DDR_ECC_Test_no_cache[1]= 0x55;
	/*normally, exception is captured about 10~100 cycles after the
	access violation. Dummy operations added here to wait for it.*/
	dummy_wait(1000);	

	printf("read back value at 0x%x ...\n", (unsigned int)&DDR_ECC_Test_no_cache[1]);
	readValue = DDR_ECC_Test_no_cache[1];
	printf("read back value = 0x%x. ", readValue);
	if(readValue!=0x55)
		printf("data corrupted!!!\n");
	else
		printf("\n");
	//printf("One bit ECC error count= %d\n", DDR3ControlRegs->ONE_BIT_ECC_ERR_CNT);
	//DDR3ControlRegs->ONE_BIT_ECC_ERR_CNT= 1; //decrease error count by unaligned write

	puts("\ntest DDR ECC in cachable range...");

	printf("Fill (64-bit aligned) test buffer from 0x%x with 0\n",	(unsigned int)DDR_ECC_Test);
	EDMA_fill((unsigned int)DDR_ECC_Test, 0, 128, EDMA_CC0_CH0);
	__asm__(" ISB");

	printf("manually inject one-bit error at bit 2 of address 0x%x, and then read it back\n",
		(Uint32)&DDR_ECC_Test[0]);

	//disable ECC
	uiOrignECC_CTL= DDR3ControlRegs->ECC_CTRL;
	DDR3ControlRegs->ECC_CTRL=0;
	delay_ms(1);
	DDR_ECC_Test[0]= 0x4; //inject one-bit error at bit 2
	CP15_DCacheCleanInvalidateBuff((unsigned int)DDR_ECC_Test, 128);
	__asm__(" ISB");
	//reenable ECC
	DDR3ControlRegs->ECC_CTRL= uiOrignECC_CTL;
	delay_ms(1);

 	readValue= DDR_ECC_Test[0];
	/*normally, exception is captured about 10~100 cycles after the
	access violation. Dummy operations added here to wait for it.*/
	dummy_wait(1000);	

	printf("read back value = 0x%x\n", readValue);

	DDR_ECC_Test[0]= readValue; 	//write back the value to correct the error
	CP15_DCacheCleanInvalidateBuff((unsigned int)DDR_ECC_Test, 128);
	__asm__(" ISB");

	printf("\n!!!manually inject two-bit error at address 0x%x, and then read it back...\n",
		(Uint32)&DDR_ECC_Test[64]);

	//disable ECC
	uiOrignECC_CTL= DDR3ControlRegs->ECC_CTRL;
	DDR3ControlRegs->ECC_CTRL=0;
	delay_ms(1);
	DDR_ECC_Test[64]= 0x9; //inject one-bit error at bit 3 and 0
	CP15_DCacheCleanInvalidateBuff((unsigned int)DDR_ECC_Test, 128);
	__asm__(" ISB");
	//reenable ECC
	DDR3ControlRegs->ECC_CTRL= uiOrignECC_CTL;
	delay_ms(1);

 	readValue= DDR_ECC_Test[64];
	/*normally, exception is captured about 10~100 cycles after the
	access violation. Dummy operations added here to wait for it.*/
	dummy_wait(1000);	
	CP15_DCacheCleanInvalidateBuff((unsigned int)DDR_ECC_Test, 128);

	//printf("read back value = 0x%x\n", readValue);

}

/*DDR ECC test with EDMA*/
void DDR_ECC_test_EDMA()
{
	Uint32 uiOrignECC_CTL;

	printf("Fill (64-bit aligned) test buffer from 0x%x with 0\n",	(unsigned int)DDR_ECC_Test);
	EDMA_fill((unsigned int)DDR_ECC_Test, 0, 128, EDMA_CC0_CH0);
	__asm__(" ISB");

	printf("!!!manually inject one-bit error at bit 4 of address 0x%x, and then scrub with EDMA\n",
		(Uint32)&DDR_ECC_Test[0]);

	//disable ECC
	uiOrignECC_CTL= DDR3ControlRegs->ECC_CTRL;
	DDR3ControlRegs->ECC_CTRL=0;
	delay_ms(1);
	DDR_ECC_Test[0]= 0x10; //inject one-bit error at bit 4
	CP15_DCacheCleanInvalidateBuff((unsigned int)DDR_ECC_Test, 128);
	//reenable ECC
	DDR3ControlRegs->ECC_CTRL= uiOrignECC_CTL;
	delay_ms(1);

	EDMA_copy((Uint32)&DDR_ECC_Test[0], (Uint32)&DDR_ECC_Test[0], 64, EDMA_CC0_CH0, DMA_NO_WAIT);
	/*normally, exception is captured about 10~100 cycles after the
	access violation. Dummy operations added here to wait for it.*/
	dummy_wait(1000);	
	EDMA_wait(EDMA_CC0_CH0);

	printf("read back value after scrub = 0x%x\n", DDR_ECC_Test[0]);
	CP15_DCacheCleanInvalidateBuff((unsigned int)DDR_ECC_Test, 128);

}

/*****************************************************************************
 Prototype    : DDR_ECC_test
 Description  : DDR ECC test example
 Input        : None
 Output       : None
 Return Value : 
 
  History        :
  1.Date         : 2012/11/2
    Author       : Brighton Feng
    Modification : Created function

*****************************************************************************/
void DDR_ECC_test()
{
#if (2==CSL_DDR3_PER_CNT)
	/*for Device has two DDR3 interfaces*/
	DDR3ControlRegs= gpDDR3ControlRegs[0];
#else
	/*for Device has one DDR3 interface*/
	DDR3ControlRegs= gpDDR3ControlRegs;
#endif
	if(2>(DDR3ControlRegs->EMIF_MOD_ID_REV&CSL_EMIF4FV_EMIF_MOD_ID_REV_REG_MINOR_REVISION_MASK)
		>>CSL_EMIF4FV_EMIF_MOD_ID_REV_REG_MINOR_REVISION_SHIFT)
		return; 	//revision 1 does not support ECC very well
	
	puts("===================-DDR ECC test-================================");

	/*To avoid periodic DDR ECC scrubbing interfere this test, 
	disable EDMA channel which scrub DDR EDC*/
	gpEDMA_CC_regs[0]->TPCC_EECR= (1<<CSL_EDMACC_0_TIMER_9_INTL);

	puts("-----------DDR ECC test with CPU--------------");
	DDR_ECC_test_core(); 	/*DDR ECC test with CPU*/

	puts("-----------DDR ECC test with EDMA------------------");
	DDR_ECC_test_EDMA(); 	/*DDR ECC test with EDMA*/

	//re-enable EDMA channel for DDR scrubbing
	gpEDMA_CC_regs[0]->TPCC_ECR= (1<<CSL_EDMACC_0_TIMER_9_INTL);
	//enable channel
	gpEDMA_CC_regs[0]->TPCC_EESR= (1<<CSL_EDMACC_0_TIMER_9_INTL);
	
}

