/******************************************************************************
*
* Copyright (C) 2011 - 2015 Xilinx, Inc.  All rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* Use of the Software is limited solely to applications:
* (a) running on a Xilinx device, or
* (b) that interact with a Xilinx device through a bus or interconnect.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* XILINX  BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF
* OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
* Except as contained in this notice, the name of the Xilinx shall not be used
* in advertising or otherwise to promote the sale, use or other dealings in
* this Software without prior written authorization from Xilinx.
*
******************************************************************************/
/*****************************************************************************/
/**
* @file xbram_selftest.c
* @addtogroup bram_v4_0
* @{
*
* The implementation of the XBram driver's self test function. This SelfTest
* is only applicable if ECC is enabled.
* If ECC is not enabled then this function will return XST_SUCCESS.
* See xbram.h for more information about the driver.
* Temp change
*
* @note
*
* None
*
* <pre>
* MODIFICATION HISTORY:
*
* Ver   Who  Date     Changes
* ----- ---- -------- -----------------------------------------------
* 1.00a sa   11/24/10 First release
* 3.01a sa   01/13/12  Changed Selftest API from
*		       XBram_SelfTest(XBram *InstancePtr) to
*		       XBram_SelfTest(XBram *InstancePtr, u8 IntMask) and
*		       fixed a problem with interrupt generation for CR 639274
*		       Modified Selftest example to return XST_SUCCESS when
*		       ECC is not enabled and return XST_FAILURE when ECC is
*		       enabled and Control Base Address is zero (CR 636581)
*		       Modified Selftest to use correct CorrectableCounterBits
*		       for CR 635655
*		       Updated to check CorrectableFailingDataRegs in the case
*		       of LMB BRAM.
* 3.02a sa  04/16/12   Added test of byte and halfword read-modify-write
* 3.03a bss 05/22/13   Added Xil_DCacheFlushRange in InjectErrors API to
*		       flush the Cache after writing to BRAM (CR #719011)
* </pre>
*****************************************************************************/

/***************************** Include Files ********************************/
#include "xbram.h"
#include "xil_cache.h"
/************************** Constant Definitions ****************************/
#define TOTAL_BITS	39

/**************************** Type Definitions ******************************/

/***************** Macros (Inline Functions) Definitions ********************/
#define RD(reg)		XBram_ReadReg(InstancePtr->Config.CtrlBaseAddress, \
					XBRAM_ ## reg)
#define WR(reg, data)	XBram_WriteReg(InstancePtr->Config.CtrlBaseAddress, \
						XBRAM_ ## reg, data)

#define CHECK(reg, data, result) if (result!=XST_SUCCESS || RD(reg)!=data) \
						result = XST_FAILURE;

/************************** Variable Definitions ****************************/
static u32 PrngResult;

/************************** Function Prototypes *****************************/
static inline u32 PrngData(u32 *PrngResult);

static inline u32 CalculateEcc(u32 Data);

static void InjectErrors(XBram * InstancePtr, u32 Addr,
			 int Index1, int Index2, int Width,
			 u32 *ActualData, u32 *ActualEcc);


/*****************************************************************************/
/**
* Generate a pseudo random number.
*
* @param	The PrngResult is the previous random number in the pseudo
*		random sequence, also knwon as the seed. It is modified to
*		the calculated pseudo random number by the function.
*
* @return 	The generated pseudo random number
*
* @note		None.
*
******************************************************************************/
static inline u32 PrngData(u32 *PrngResult)
{
	*PrngResult = *PrngResult * 0x77D15E25 + 0x3617C161;
	return *PrngResult;
}


/*****************************************************************************/
/**
* Calculate ECC from Data.
*
* @param	The Data Value
*
* @return 	The calculated ECC
*
* @note		None.
*
******************************************************************************/
static inline u32 CalculateEcc(u32 Data)
{
	unsigned char c[7], d[32];
	u32 Result = 0;
	int Index;

	for (Index = 0; Index < 32; Index++) {
		d[31 - Index] = Data & 1;
		Data = Data >> 1;
	}

	c[0] =  d[0]  ^ d[1]  ^ d[3]  ^ d[4]  ^ d[6]  ^ d[8]  ^ d[10] ^ d[11] ^
		d[13] ^ d[15] ^ d[17] ^ d[19] ^ d[21] ^ d[23] ^ d[25] ^ d[26] ^
		d[28] ^ d[30];

	c[1] =  d[0]  ^ d[2]  ^ d[3]  ^ d[5]  ^ d[6]  ^ d[9]  ^ d[10] ^ d[12] ^
		d[13] ^ d[16] ^ d[17] ^ d[20] ^ d[21] ^ d[24] ^ d[25] ^ d[27] ^
		d[28] ^ d[31];

	c[2] =  d[1]  ^ d[2]  ^ d[3]  ^ d[7]  ^ d[8]  ^ d[9]  ^ d[10] ^ d[14] ^
		d[15] ^ d[16] ^ d[17] ^ d[22] ^ d[23] ^ d[24] ^ d[25] ^ d[29] ^
		d[30] ^ d[31];

	c[3] =  d[4]  ^ d[5]  ^ d[6]  ^ d[7]  ^ d[8]  ^ d[9]  ^ d[10] ^ d[18] ^
		d[19] ^ d[20] ^ d[21] ^ d[22] ^ d[23] ^ d[24] ^ d[25];

	c[4] =  d[11] ^ d[12] ^ d[13] ^ d[14] ^ d[15] ^ d[16] ^ d[17] ^ d[18] ^
		d[19] ^ d[20] ^ d[21] ^ d[22] ^ d[23] ^ d[24] ^ d[25];

	c[5] = d[26] ^ d[27] ^ d[28] ^ d[29] ^ d[30] ^ d[31];

	c[6] =  d[0]  ^ d[1]  ^ d[2]  ^ d[3]  ^ d[4]  ^  d[5] ^  d[6] ^ d[7]  ^
		d[8]  ^ d[9]  ^ d[10] ^ d[11] ^ d[12] ^ d[13] ^ d[14] ^ d[15] ^
		d[16] ^ d[17] ^ d[18] ^ d[19] ^ d[20] ^ d[21] ^ d[22] ^ d[23] ^
		d[24] ^ d[25] ^ d[26] ^ d[27] ^ d[28] ^ d[29] ^ d[30] ^ d[31] ^
		c[5]  ^ c[4]  ^ c[3] ^  c[2]  ^ c[1]  ^ c[0];

	for (Index = 0; Index < 7; Index++) {
		Result = Result << 1;
		Result |= c[Index] & 1;
	}

	return Result;
}

/*****************************************************************************/
/**
* Get the expected actual data read in case of uncorrectable errors.
*
* @param	The injected data value including errors (if any)
* @param	The syndrome (calculated ecc ^ actual ecc read)
*
* @return 	The actual data value read
*
* @note		None.
*
******************************************************************************/
static inline u32 UncorrectableData(u32 Data, u8 Syndrome)
{
	switch (Syndrome) {
		case 0x03: return Data ^ 0x00000034;
		case 0x05: return Data ^ 0x001a2000;
		case 0x09: return Data ^ 0x0d000000;
		case 0x0d: return Data ^ 0x00001a00;

		case 0x11: return Data ^ 0x60000000;
		case 0x13: return Data ^ 0x00000003;
		case 0x15: return Data ^ 0x00018000;
		case 0x19: return Data ^ 0x00c00000;
		case 0x1d: return Data ^ 0x00000180;

		case 0x21: return Data ^ 0x80000000;
		case 0x23: return Data ^ 0x00000008;
		case 0x25: return Data ^ 0x00040000;
		case 0x29: return Data ^ 0x02000000;
		case 0x2d: return Data ^ 0x00000400;

		case 0x31: return Data ^ 0x10000000;
		case 0x35: return Data ^ 0x00004000;
		case 0x39: return Data ^ 0x00200000;
		case 0x3d: return Data ^ 0x00000040;
	}
	return Data;
}

/*****************************************************************************/
/**
* Inject errors using the hardware fault injection functionality, and write
* random data and read it back using the indicated location.
*
* @param	InstancePtr is a pointer to the XBram instance to
*		be worked on.
* @param	The Addr is the indicated memory location to use
* @param	The Index1 is the bit location of the first injected error
* @param	The Index2 is the bit location of the second injected error
* @param	The Width is the data byte width
* @param	The ActualData is filled in with expected data for checking
* @param	The ActualEcc is filled in with expected ECC for checking
*
* @return 	None
*
* @note		None.
*
******************************************************************************/
static void InjectErrors(XBram * InstancePtr, u32 Addr,
			 int Index1, int Index2, int Width,
			 u32 *ActualData, u32 *ActualEcc)
{
	u32 InjectedData = 0;
	u32 InjectedEcc = 0;
	u32 RandomData = PrngData(&PrngResult);

	if (Index1 < 32) {
		InjectedData = 1 << Index1;
	} else {
		InjectedEcc = 1 << (Index1 - 32);
	}

	if (Index2 < 32) {
		InjectedData |= (1 << Index2);
	} else {
		InjectedEcc |= 1 << (Index2 - 32);
	}

	WR(FI_D_0_OFFSET, InjectedData);
	WR(FI_ECC_0_OFFSET, InjectedEcc);

	XBram_Out32(Addr, RandomData);
	Xil_DCacheFlushRange(Addr, 4);
	switch (Width) {
		case 1: /* Byte - Write to do Read-Modify-Write */
			XBram_Out8(Addr, PrngData(&PrngResult) & 0xFF);
			break;
		case 2: /* Halfword - Write to do Read-Modify-Write */
			XBram_Out16(Addr, PrngData(&PrngResult) & 0xFFFF);
			break;
		case 4:	/* Word - Read */
			(void) XBram_In32(Addr);
			break;
	}
	*ActualData = InjectedData ^ RandomData;
	*ActualEcc  = InjectedEcc ^ CalculateEcc(RandomData);
}


/*****************************************************************************/
/**
* Run a self-test on the driver/device. Unless fault injection is implemented
* in hardware, this function only does a minimal test in which available
* registers (if any) are written and read.
*
* With fault injection, all possible single-bit and double-bit errors are
* injected, and checked to the extent possible, given the implemented hardware.
*
* @param	InstancePtr is a pointer to the XBram instance.
* @param	IntMask is the interrupt mask to use. When testing
*		with interrupts, this should be set to allow interrupt
*		generation, otherwise it should be 0.
*
* @return
*		- XST_SUCCESS if fault injection/detection is working properly OR
*		  if ECC is Not Enabled in the HW.
*		- XST_FAILURE if the injected fault is not correctly detected or
*		  the Control Base Address is Zero when ECC is enabled.
*		.
*
*		If the BRAM device is not present in the
*		hardware a bus error could be generated. Other indicators of a
*		bus error, such as registers in bridges or buses, may be
*		necessary to determine if this function caused a bus error.
*
* @note		None.
*
******************************************************************************/
int XBram_SelfTest(XBram *InstancePtr, u8 IntMask)
{
	Xil_AssertNonvoid(InstancePtr != NULL);
	Xil_AssertNonvoid(InstancePtr->IsReady == XIL_COMPONENT_IS_READY);



	if (InstancePtr->Config.EccPresent == 0) {
		return (XST_SUCCESS);
	}

	if (InstancePtr->Config.CtrlBaseAddress == 0) {
		return (XST_SUCCESS);
	}

	/*
	 * Only 32-bit data width is supported as of yet. 64-bit and 128-bit
	 * widths will be supported in future.
	 */
	if (InstancePtr->Config.DataWidth != 32)
		return (XST_SUCCESS);

	/*
	 * Read from the implemented readable registers in the hardware device.
	 */
	if (InstancePtr->Config.CorrectableFailingRegisters) {
		(void) RD(CE_FFA_0_OFFSET);
	}
	if (InstancePtr->Config.CorrectableFailingDataRegs) {
		(void) RD(CE_FFD_0_OFFSET);
		(void) RD(CE_FFE_0_OFFSET);
	}
	if (InstancePtr->Config.UncorrectableFailingRegisters) {
		(void) RD(UE_FFA_0_OFFSET);
	}
	if (InstancePtr->Config.UncorrectableFailingDataRegs) {
		(void) RD(UE_FFD_0_OFFSET);
		(void) RD(UE_FFE_0_OFFSET);
	}

	/*
	 * Write and read the implemented read/write registers in the hardware
	 * device.
	 */
	if (InstancePtr->Config.EccStatusInterruptPresent) {
		WR(ECC_EN_IRQ_OFFSET, 0);
		if (RD(ECC_EN_IRQ_OFFSET) != 0) {
			return (XST_FAILURE);
		}
	}

	if (InstancePtr->Config.CorrectableCounterBits > 0) {
		u32 Value;

		/* Calculate counter max value */
		if (InstancePtr->Config.CorrectableCounterBits == 32) {
		 	Value = 0xFFFFFFFF;
		} else {
		 	Value = (1 <<
		 		InstancePtr->Config.CorrectableCounterBits) - 1;
		 }

		WR(CE_CNT_OFFSET, Value);
		if (RD(CE_CNT_OFFSET) != Value) {
			return (XST_FAILURE);
		}

		WR(CE_CNT_OFFSET, 0);
		if (RD(CE_CNT_OFFSET) != 0) {
			return (XST_FAILURE);
		}
	}

	/*
	 * If fault injection is implemented, inject all possible single-bit
	 * and double-bit errors, and check all observable effects.
	 */
	if (InstancePtr->Config.FaultInjectionPresent &&
	    InstancePtr->Config.WriteAccess != 0) {

	    const u32 Addr[2] = {InstancePtr->Config.MemBaseAddress &
					0xfffffffc,
				 InstancePtr->Config.MemHighAddress &
					0xfffffffc};
	    u32 SavedWords[2];
	    u32 ActualData;
	    u32 ActualEcc;
	    u32 CounterValue = 0;
	    u32 CounterMax;
	    int WordIndex = 0;
	    int Result = XST_SUCCESS;
	    int Index1;
	    int Index2;
	    int Width;

	    PrngResult = 42; /* Random seed */

	    /* Save two words in BRAM used for test */
	    SavedWords[0] = XBram_In32(Addr[0]);
	    SavedWords[1] = XBram_In32(Addr[1]);

	    for (Width = 1; Width <= 4; Width <<= 1) {
		/* Calculate counter max value */
		if (InstancePtr->Config.CorrectableCounterBits == 32) {
			CounterMax = 0xFFFFFFFF;
		} else {
			CounterMax =(1 <<
				InstancePtr->Config.CorrectableCounterBits) - 1;
		}

		/* Inject and check all single bit errors */
		for (Index1 = 0; Index1 < TOTAL_BITS; Index1++) {
			/* Save counter value */
			if (InstancePtr->Config.CorrectableCounterBits > 0) {
				CounterValue = RD(CE_CNT_OFFSET);
			}

			/* Inject single bit error */
			InjectErrors(InstancePtr, Addr[WordIndex], Index1,
				     Index1, Width, &ActualData, &ActualEcc);

			/* Check that CE is set */
			if (InstancePtr->Config.EccStatusInterruptPresent) {
				CHECK(ECC_STATUS_OFFSET,
					XBRAM_IR_CE_MASK, Result);
			}

			/* Check that address, data, ECC are correct */
			if (InstancePtr->Config.CorrectableFailingRegisters) {
				CHECK(CE_FFA_0_OFFSET, Addr[WordIndex], Result);
 			}
 			/* Checks are only for LMB BRAM */
 			if (InstancePtr->Config.CorrectableFailingDataRegs) {
  				CHECK(CE_FFD_0_OFFSET, ActualData, Result);
  				CHECK(CE_FFE_0_OFFSET, ActualEcc, Result);
  			}

			/* Check that counter has incremented */
			if (InstancePtr->Config.CorrectableCounterBits > 0 &&
				CounterValue < CounterMax) {
					CHECK(CE_CNT_OFFSET,
					CounterValue + 1, Result);
			}

			/* Restore correct data in the used word */
			XBram_Out32(Addr[WordIndex], SavedWords[WordIndex]);

			/* Allow interrupts to occur */
			/* Clear status register */
			if (InstancePtr->Config.EccStatusInterruptPresent) {
				WR(ECC_EN_IRQ_OFFSET, IntMask);
				WR(ECC_STATUS_OFFSET, XBRAM_IR_ALL_MASK);
				WR(ECC_EN_IRQ_OFFSET, 0);
			}

			/* Switch to the other word */
			WordIndex = WordIndex ^ 1;

			if (Result != XST_SUCCESS) break;

		}

		if (Result != XST_SUCCESS) {
			return XST_FAILURE;
		}

		for (Index1 = 0; Index1 < TOTAL_BITS; Index1++) {
			for (Index2 = 0; Index2 < TOTAL_BITS; Index2++) {
			    if (Index1 != Index2) {
				/* Inject double bit error */
				InjectErrors(InstancePtr,
					Addr[WordIndex],
						Index1, Index2, Width,
						&ActualData,
						&ActualEcc);

				/* Check that UE is set */
				if (InstancePtr->Config.
				    EccStatusInterruptPresent) {
					CHECK(ECC_STATUS_OFFSET,
					XBRAM_IR_UE_MASK,
					Result);
				}

				/* Check that address, data, ECC are correct */
				if (InstancePtr->Config.
				    UncorrectableFailingRegisters) {
					CHECK(UE_FFA_0_OFFSET, Addr[WordIndex],
							Result);
					CHECK(UE_FFD_0_OFFSET,
						ActualData, Result);
					CHECK(UE_FFE_0_OFFSET, ActualEcc,
						Result);
					}

				/* Restore correct data in the used word */
				XBram_Out32(Addr[WordIndex],
						SavedWords[WordIndex]);

				/* Allow interrupts to occur */
				/* Clear status register */
				if (InstancePtr->Config.
				    EccStatusInterruptPresent) {
					WR(ECC_EN_IRQ_OFFSET, IntMask);
					WR(ECC_STATUS_OFFSET,
						XBRAM_IR_ALL_MASK);
					WR(ECC_EN_IRQ_OFFSET, 0);
				}

				/* Switch to the other word */
				WordIndex = WordIndex ^ 1;
			    }
				if (Result != XST_SUCCESS) break;
			}
			if (Result != XST_SUCCESS) break;
		}

		/* Check saturation of correctable error counter */
		if (InstancePtr->Config.CorrectableCounterBits > 0 &&
			Result == XST_SUCCESS) {

				WR(CE_CNT_OFFSET, CounterMax);

				InjectErrors(InstancePtr, Addr[WordIndex], 0, 0,
					4, &ActualData, &ActualEcc);

				CHECK(CE_CNT_OFFSET, CounterMax, Result);
		}

		/* Restore the two words used for test */
		XBram_Out32(Addr[0], SavedWords[0]);
		XBram_Out32(Addr[1], SavedWords[1]);

		/* Clear the Status Register. */
		if (InstancePtr->Config.EccStatusInterruptPresent) {
			WR(ECC_STATUS_OFFSET, XBRAM_IR_ALL_MASK);
		}

		/* Set Correctable Counter to zero */
		if (InstancePtr->Config.CorrectableCounterBits > 0) {
			WR(CE_CNT_OFFSET, 0);
		}

		if (Result != XST_SUCCESS) break;

	    } /* Width loop */

	    return (Result);
	}

	return (XST_SUCCESS);
}

/** @} */
