/******************************************************************************
* Copyright (C) 2011 - 2022 Xilinx, Inc.  All rights reserved.
* Copyright (C) 2023 Advanced Micro Devices, Inc. All Rights Reserved.
* SPDX-License-Identifier: MIT
******************************************************************************/

/*****************************************************************************/
/**
* @file xbram_selftest.c
* @addtogroup bram Overview
* @{
*
* 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)
* 4.3   aru 04/09/19   Used UINTPTR instead of u32 for Addr in XBram_SelfTest()
* 4.5    mn 04/12/20   Resolve build warnings for ARMCC toolchain
*
* </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, UINTPTR 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 known 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;
}

/*****************************************************************************/
/**
* 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, const UINTPTR 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);
		}
	}
	xil_printf("ssd %s %d\n\r", __func__, __LINE__);
	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) {

		static u32 Addr[2];
		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;

		Addr[0] = InstancePtr->Config.MemBaseAddress &
			  (UINTPTR)0xfffffffffffffffc;
		Addr[1] = InstancePtr->Config.MemHighAddress &
			  (UINTPTR)0xfffffffffffffffc;
		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);
}

/** @} */
