/**
*   @copyright       © 2026 Microchip Technology Inc.
    Microchip Technology Inc. and its subsidiaries. You may use this
    software and any derivatives exclusively with Microchip products.

    THIS SOFTWARE IS SUPPLIED BY MICROCHIP "AS IS". NO WARRANTIES, WHETHER
    EXPRESS, IMPLIED OR STATUTORY, APPLY TO THIS SOFTWARE, INCLUDING ANY IMPLIED
    WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY, AND FITNESS FOR A
    PARTICULAR PURPOSE, OR ITS INTERACTION WITH MICROCHIP PRODUCTS, COMBINATION
    WITH ANY OTHER PRODUCTS, OR USE IN ANY APPLICATION.

    IN NO EVENT WILL MICROCHIP BE LIABLE FOR ANY INDIRECT, SPECIAL, PUNITIVE,
    INCIDENTAL OR CONSEQUENTIAL LOSS, DAMAGE, COST OR EXPENSE OF ANY KIND
    WHATSOEVER RELATED TO THE SOFTWARE, HOWEVER CAUSED, EVEN IF MICROCHIP HAS
    BEEN ADVISED OF THE POSSIBILITY OR THE DAMAGES ARE FORESEEABLE. TO THE
    FULLEST EXTENT ALLOWED BY LAW, MICROCHIP'S TOTAL LIABILITY ON ALL CLAIMS IN
    ANY WAY RELATED TO THIS SOFTWARE WILL NOT EXCEED THE AMOUNT OF FEES, IF ANY,
    THAT YOU HAVE PAID DIRECTLY TO MICROCHIP FOR THIS SOFTWARE.

    MICROCHIP PROVIDES THIS SOFTWARE CONDITIONALLY UPON YOUR ACCEPTANCE OF THESE
    TERMS. 
*
*************************************************************************/

/**
 *  @file       diag_flash_crc_test.c
 *  @version    1.0
 *  @brief      This file has the implementation for the FLASH crc
 *		        test diagnostics. @see diag_flash.h
 *
 *************************************************************************/

#include <xc.h>
#include <stddef.h>
#include "diag_flash.h"
#include "diag_flash_shared.h"

#define DIAG_FLASH_TOTAL_INSTRUCTIONS   131072U
#define DIAG_FLASH_STD_CRC32_POLYNOMIAL ((uint32_t)0x04C11DB7U)
#define DIAG_FLASH_STD_CRC32_SEED_VALUE ((uint32_t)0xFFFFFFFFU) // direct initial value

/*Macros for TEST MODE SCENARIOS*/
#define DIAG_FLASH_NORMAL_MODE              0x91U
#define DIAG_FLASH_ERROR_MODE_TEST          0x92U

static uint32_t testModeCrcError = DIAG_FLASH_NORMAL_MODE;

static uint32_t DIAG_FLASH_ReverseBitOrder(uint32_t data)
{
    uint32_t maskin;
    uint32_t maskout;
    uint32_t result = 0U;
    uint32_t count;
    maskin = 0x80000000U;
    maskout = 0x00000001U;
    for(count = 0U; count < 32U; count++)
    {
        if((data & maskin)!= 0U)
        {
            result |= maskout;
        }
        maskin >>= 1U;
        maskout <<= 1U;
    }
    return result;
}

static uint32_t DIAG_FLASH_StandardCRCCalculate(uint32_t *flashBlockStart, uint32_t *flashBlockEnd)
{
    uint32_t lengthStart, lengthEnd, length;
    uint32_t crcResultCRC32;
    uint32_t count;
    
	/*LDRA_EXCLUDE 439 S D: <justification start> The address is valid, violation ignored <justification end> */
    lengthStart = (uint32_t)flashBlockStart;
    /*LDRA_EXCLUDE 439 S D: <justification start> The address is valid, violation ignored <justification end> */
    lengthEnd = (uint32_t)flashBlockEnd;
    length = ((lengthEnd + 1U) - (lengthStart))/(4U);
    
    // standard CRC-32
    CRCCON = 0U;
    CRCCONbits.MOD = 1U; // alternate mode
    CRCCONbits.ON = 1U; // enable CRC
    CRCCONbits.CRCISEL = 0U; // interrupt when all shifts are done
    CRCCONbits.LENDIAN = 1U; // little endian
    CRCCONbits.DWIDTH = (32-1); // 32-bit data width
    CRCCONbits.PLEN = (32-1); // 32-bit polynomial order
    CRCXOR = DIAG_FLASH_STD_CRC32_POLYNOMIAL; // set polynomial
    CRCWDAT = DIAG_FLASH_STD_CRC32_SEED_VALUE; // set initial value
    
    if(testModeCrcError == DIAG_FLASH_NORMAL_MODE)
    {
        CRCCONbits.CRCGO = 1U; // start CRC calculation
    }
    else
    {
        CRCCONbits.CRCGO = 0U;
    }

	for(count = 0; count <= DIAG_FLASH_TOTAL_INSTRUCTIONS; count++)
    {
        if(CRCCONbits.CRCFUL == 0U) // wait if FIFO is full
        {
            length--;
            if(length == 0U)
            {
                break;
            }
            else
            {
                /*LDRA_EXCLUDE 9 S D: <justification start> Pointer arithmetic used to obtain pointer to the correct address, violation ignored <justification end> */
                /*LDRA_EXCLUDE 30 S D: <justification start> Pointer arithmetic used to obtain pointer to the correct address, violation ignored <justification end> */
                CRCDAT = *flashBlockStart++; // 32-bit word access to FIFO
            }
        }
        else
        {
            //Do Nothing
        }
    }
    CRCCONbits.CRCGO = 0U; // suspend CRC calculation
    IFS4bits.CRCIF = 0U; // clear the interrupt flag
    CRCDAT = *flashBlockStart; // write last data into FIFO
    
    if(testModeCrcError == DIAG_FLASH_NORMAL_MODE)
    {
        CRCCONbits.CRCGO = 1U; // start CRC calculation
    }
    else
    {
        CRCCONbits.CRCGO = 0U;
    }
    
	for(count = 0; count <= DIAG_FLASH_TOTAL_INSTRUCTIONS; count++)
    {
		if(IFS4bits.CRCIF == 1U)// wait until shifts are done
		{
			break;
		}
		else
		{
			//Do nothing
		}
	}	
    crcResultCRC32 = CRCWDAT; // get the final CRC result
    // Reverse CRC value bit order and invert
    crcResultCRC32 = ~DIAG_FLASH_ReverseBitOrder(crcResultCRC32);

    return crcResultCRC32;
}

uint32_t DIAG_FLASH_CRCCalculate(uint32_t *flashBlockStart, uint32_t *flashBlockEnd)
{    
    uint32_t count;
    
    /*Configure Flash CRC to calculate the CRC for the desired flash page*/
    IFS0bits.NVMCRCIF = 0U;
    NVMCRCCONbits.ON = 0U;
    NVMCRCCONbits.CRCIDL = 0U;
    NVMCRCCONbits.DELAY = 0U;
    if(testModeCrcError == DIAG_FLASH_NORMAL_MODE)
    {
        NVMCRCCONbits.ON = 1U;
    }
    else
    {
        NVMCRCCONbits.ON = 0U;
    }
	/*LDRA_EXCLUDE 439 S <justification start>The address goes in as a value to the register. Violation ignored<justification end>*/
    NVMCRCST = (uint32_t)flashBlockStart;
	/*LDRA_EXCLUDE 439 S <justification start>The address goes in as a value to the register. Violation ignored<justification end>*/
    NVMCRCEND = (uint32_t)flashBlockEnd;   
    NVMCRCSEED = 0x00000000U;
    /*LDRA_EXCLUDE 105 D: <justification start> This sfr register is updated as per the design, violation ignored <justification end> */
	NVMCRCCONbits.START = 1U;
    
    //Wait until CRC is calculated
    for(count = 0U; count <= DIAG_FLASH_TOTAL_INSTRUCTIONS; count++)
    {
        if(IFS0bits.NVMCRCIF == 1U)
        {
            break;
        }
        else
        {
            //Do nothing
        }
    }		
    return NVMCRCDATA;
}

uint32_t DIAG_FLASH_CRCPractice(uint32_t *flashBlockStart, uint32_t *flashBlockEnd, uint32_t previousCRC)
{
    uint32_t standardCRCData;
    uint32_t nvmCRCData;
    uint32_t status;
    uint32_t addressStart, addressEnd;
    
    if((flashBlockStart != NULL) && (flashBlockEnd != NULL))
    {
		/*LDRA_EXCLUDE 439 S D: <justification start> The address is valid, violation ignored <justification end> */
        addressStart = ((uint32_t)flashBlockStart & (uint32_t)(0xFFFFF000U));
        /*LDRA_EXCLUDE 439 S D: <justification start> The address is valid, violation ignored <justification end> */
		addressEnd = ((uint32_t)flashBlockEnd | (uint32_t)(0x00000FFFU));

        if(addressStart < addressEnd)
        {
            /*LDRA_EXCLUDE 91 D: <justification start> This variable is updated as per the design, violation ignored <justification end> */
            /*LDRA_EXCLUDE 440 S D: <justification start> The address is valid, violation ignored <justification end> */
            /*LDRA_EXCLUDE 440 S D: <justification start> The address is valid, violation ignored <justification end> */
			standardCRCData = DIAG_FLASH_StandardCRCCalculate((uint32_t *)addressStart, (uint32_t *)addressEnd);
            /*LDRA_EXCLUDE 440 S D: <justification start> The address is valid, violation ignored <justification end> */
            /*LDRA_EXCLUDE 440 S D: <justification start> The address is valid, violation ignored <justification end> */
			nvmCRCData = DIAG_FLASH_CRCCalculate((uint32_t *)addressStart, (uint32_t *)addressEnd);

            //Check if the calculated CRC matches the previous calculated CRC
            if((nvmCRCData == previousCRC) && (nvmCRCData == standardCRCData))
            {
                status = DIAG_PASS;
            }
            else
            {
                status = DIAG_FAIL;
            }
        }else
        {
            status = DIAG_FAIL;
        }
    }
    else
    {
        status = DIAG_INVALID_PARAM;
    }
    
    return status;
}

void DIAG_FLASH_SetTestModeCrcError(uint32_t setTestMode)
{
    testModeCrcError = setTestMode;
}
