/**
*   @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_timer_linearity_test.c
 *  @version    1.0
 *  @brief      This file has the implementation for the TIMER Linearity 
 *		        test diagnostics. 
 *  @see        diag_timer.h
 *************************************************************************/

#include <stddef.h>
#include <stdint.h>
#include "diag.h"
#include "diag_timer.h"
#include "diag_timer_shared.h"

/**
 @def DIAG_TIMER_LINEARITY_ITERATIONS
 The Timer linearity diagnostic needs to run a block of instructions to get a
 notion of elapsed time during its run. This macro defines the number iterations
 a block is being executed.
 */
#define DIAG_TIMER_LINEARITY_ITERATIONS 5U

/**
 @def DIAG_TIMER_LINEARITY_COUNT_NORMAL
 The Timer linearity diagnostic needs to run a block of instructions to get a
 notion of elapsed time during its run.  This macro defines the length of that
 instruction block.
 */
#define DIAG_TIMER_LINEARITY_COUNT_NORMAL 200U

/**
 @def DIAG_TIMER_LINEARITY_COUNT_TESTMODE
 The Timer linearity diagnostic needs to run a block of instructions to get a
 notion of elapsed time during its run.  This macro defines the length of that
 instruction block while the test mode for timer is being called.
 */
#define DIAG_TIMER_LINEARITY_COUNT_TESTMODE 1000U

/*Macros for TEST MODE SCENARIOS*/
#define DIAG_TIMER_TEST_NORMAL              0x91U
#define DIAG_TIMER_TEST_DISABLE_TIMER       0x92U
#define DIAG_TIMER_TEST_TOLERANCE           0x93U
#define DIAG_TIMER_TEST_OVERFLOW            0x94U     

/*Timer Counts for TEST MODE*/
#define DIAG_TIMER_TEST_COUNTS              0xFFD0U

//Variables for storing and restoring the timer module registers
static uint32_t storeLinearityPrescalerValue = 0x0000U;
static uint32_t storeLinearityPeriodValue = 0x0000U;
static uint32_t storeLinearityTimerValue = 0x0000U;

static uint32_t testModeLinearity = DIAG_TIMER_TEST_NORMAL;/*Normal Mode*/

static void DIAG_TIMER_LinearityStoreValues(DIAG_UTILITY_TIMER_INSTANCE tmrId)
{   
    //Store the values before executing the diagnostics
    DIAG_UTIL_TIMER_DisableTimer(tmrId);
    storeLinearityPrescalerValue = DIAG_UTIL_TIMER_GetPrescaler(tmrId);
    storeLinearityPeriodValue = DIAG_UTIL_TIMER_GetPeriodCount(tmrId);
    storeLinearityTimerValue = DIAG_UTIL_TIMER_GetTMRValue(tmrId);  
}

static void DIAG_TIMER_LinearityRestoreValues(DIAG_UTILITY_TIMER_INSTANCE tmrId)
{    
    //Restore the values after the diagnostics is executed
    DIAG_UTIL_TIMER_DisableTimer(tmrId);
    (void)DIAG_UTIL_TIMER_SetPrescaler(tmrId, storeLinearityPrescalerValue);
    (void)DIAG_UTIL_TIMER_SetPeriodCount(tmrId, storeLinearityPeriodValue);
    (void)DIAG_UTIL_TIMER_SetTMRValue(tmrId, storeLinearityTimerValue);  
}

static void DIAG_TIMER_LinearitySetValues(DIAG_UTILITY_TIMER_INSTANCE tmrId)
{
    /*Default Values used for Diagnostics*/
    uint32_t setPrescalerValue = 0x00U;
    uint32_t setPeriodValue = 0xFFFFU;
    uint32_t setTimerValue = 0x0000U;    
    
    //Set the predefined values required for the diagnostics
    DIAG_UTIL_TIMER_DisableTimer(tmrId);
    (void)DIAG_UTIL_TIMER_SetPrescaler(tmrId, setPrescalerValue);
    (void)DIAG_UTIL_TIMER_SetPeriodCount(tmrId, setPeriodValue);
    (void)DIAG_UTIL_TIMER_SetTMRValue(tmrId, setTimerValue);  
}

static uint32_t DIAG_TIMER_GetCountElapsed(DIAG_UTILITY_TIMER_INSTANCE tmrId, 
                                    uint32_t repeatCount)
{
    uint32_t aTmrCount = 0x0000U;
    uint32_t bTmrCount = 0x0000U;
    uint32_t countElapsed = 0x0000U;
	
    aTmrCount = DIAG_UTIL_TIMER_GetTMRValue(tmrId);

	/*LDRA_EXCLUDE 496 S <justification start> This is a compiler builtin function and so 496 S is ignored <justification end> */
    __builtin_repeat_nop(repeatCount);
	
    bTmrCount = DIAG_UTIL_TIMER_GetTMRValue(tmrId);
    
    //Check if timer counts are incrementing on a positive scale
    if(aTmrCount > bTmrCount )
    {
        countElapsed = 0U;
    }
    else
    {
        countElapsed = bTmrCount - aTmrCount;
    }
    
    return countElapsed;
}

static bool DIAG_TIMER_GetDeviations(DIAG_UTILITY_TIMER_INSTANCE tmrId,
                                                uint32_t tolerance)
{
    uint32_t aElapsedCount = 0x0000U;
    uint32_t bElapsedCount = 0x0000U;
    bool status = 0U;
    uint32_t currentDeviation;
    uint32_t setTestTimerValue = DIAG_TIMER_TEST_COUNTS; 
    uint32_t repeatCount = DIAG_TIMER_LINEARITY_COUNT_NORMAL;
    
    //Set the predefined values required for the diagnostics
    DIAG_TIMER_LinearitySetValues(tmrId);
    
    //Select the parameters based on normal mode or test mode
    switch(testModeLinearity)
    {
        case DIAG_TIMER_TEST_NORMAL:/*Normal Mode*/
                        repeatCount = DIAG_TIMER_LINEARITY_COUNT_NORMAL;
                        DIAG_UTIL_TIMER_EnableTimer(tmrId);
                        break;
        case DIAG_TIMER_TEST_DISABLE_TIMER:/*Test Disable Timer Mode*/
                        repeatCount = DIAG_TIMER_LINEARITY_COUNT_NORMAL;            
                        DIAG_UTIL_TIMER_DisableTimer(tmrId);
                        break;
        case DIAG_TIMER_TEST_OVERFLOW:/*Test Timer Overflow Mode*/
                        (void)DIAG_UTIL_TIMER_SetTMRValue(tmrId, setTestTimerValue);
                        repeatCount = DIAG_TIMER_LINEARITY_COUNT_NORMAL;
                        DIAG_UTIL_TIMER_EnableTimer(tmrId);
                        break;                             
        case DIAG_TIMER_TEST_TOLERANCE:/*Test Timer Beyond Tolerance Mode*/
                        repeatCount = DIAG_TIMER_LINEARITY_COUNT_TESTMODE;
                        DIAG_UTIL_TIMER_EnableTimer(tmrId);            
                        break;                           
        default:// The control should not reach here.
                        repeatCount = DIAG_TIMER_LINEARITY_COUNT_NORMAL;             
                        DIAG_UTIL_TIMER_EnableTimer(tmrId);
                        break;
    }
            
    aElapsedCount = (uint32_t)(DIAG_TIMER_GetCountElapsed(tmrId, repeatCount));

    //Check if the timer counts are not incrementing
    if((aElapsedCount == 0U))
    {
        /*LDRA_EXCLUDE 151 D <justification start> Variable used for calculation and found correct, violation is ignored <justification end>*/
        status = 0U;
    }
    else
    {
        //Set the predefined values required for the diagnostics
        DIAG_TIMER_LinearitySetValues(tmrId);
        
        //Select the parameters based on normal mode or test mode
        switch(testModeLinearity)
        {
            case DIAG_TIMER_TEST_NORMAL:/*Normal Mode*/
                            repeatCount = DIAG_TIMER_LINEARITY_COUNT_NORMAL;
                            DIAG_UTIL_TIMER_EnableTimer(tmrId);
                            break;                            
            case DIAG_TIMER_TEST_TOLERANCE:/*Test Timer Beyond Tolerance Mode*/
                            repeatCount = DIAG_TIMER_LINEARITY_COUNT_NORMAL;
                            DIAG_UTIL_TIMER_EnableTimer(tmrId);            
                            break;                           
            default:// The control should not reach here.
                            repeatCount = DIAG_TIMER_LINEARITY_COUNT_NORMAL;             
                            DIAG_UTIL_TIMER_DisableTimer(tmrId);
                            break;
        }
        /*LDRA_EXCLUDE 91 D <justification start> Variable used for calculation and found correct, violation is ignored <justification end>*/
        bElapsedCount = (uint32_t)(DIAG_TIMER_GetCountElapsed(tmrId, repeatCount));
    }

    //Check if the timer counts are not incrementing
    if((bElapsedCount == 0U) )
    {
        /*LDRA_EXCLUDE 151 D <justification start> Variable used for calculation and found correct, violation is ignored <justification end>*/
        status = 0U;
    }
    else
    {
        if(aElapsedCount > bElapsedCount)
        {
            /*LDRA_EXCLUDE 488 S <justification start> The calculation shall not be below zero, violation is ignored <justification end> */
            currentDeviation = (uint32_t)(aElapsedCount - bElapsedCount);
        }
        else
        {
            currentDeviation = (uint32_t)(bElapsedCount - aElapsedCount);
        }
        /* The currentDeviation has to be ensured to be within the tolerance
        range, to declare a Pass of this test */
        if(currentDeviation <= tolerance)
        {
            status = 1U;
        }
        else
        {
            status = 0U;
        }
    }
    
    return status;
}

uint32_t DIAG_TIMER_LinearityTest(DIAG_UTILITY_TIMER_INSTANCE tmrId,    
                                    uint32_t tolerance)
{
    uint32_t status;
    uint16_t iterations;
    
	if((uint32_t)tmrId < (uint32_t)DIAG_UTILITY_TIMER_TOTAL)
	{   
        //Store the values before executing the diagnostics
        DIAG_TIMER_LinearityStoreValues(tmrId);
            
        //Execute the block for a set number of iterations
        for(iterations = 0;iterations < DIAG_TIMER_LINEARITY_ITERATIONS; iterations++)
        {
            if(0U == DIAG_TIMER_GetDeviations(tmrId, tolerance))
            {
				/*LDRA_EXCLUDE 151 D <justification start> Result assigned a value and is checked functionally and found correct, violation is ignored <justification end>*/
                status = DIAG_FAIL;
                break;
            }
            else
            {
				/*LDRA_EXCLUDE 151 D <justification start> Result assigned a value and is checked functionally and found correct, violation is ignored <justification end>*/
                status = DIAG_PASS;
            }
        }

        //Restore the values after the diagnostics is executed
        DIAG_TIMER_LinearityRestoreValues(tmrId);
    }
    else
    {
        status = DIAG_INVALID_PARAM;
    }
    
    return status;
}

void DIAG_TIMER_SetTestModeLinearity(uint32_t setTestMode)
{
    testModeLinearity = setTestMode;
}
