/**
*   @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_functional_test.c
 *  @version    1.0
 *  @brief      This file has the implementation for the TIMER Functional 
 *		        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_INST_NORMAL
 The Timer functional 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_INST_NORMAL  40U

/**
 @def DIAG_TIMER_INST_TEST_FAST
 The Timer functional 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 fast is being called.
 */
#define DIAG_TIMER_INST_TEST_FAST  1000U

/**
 @def DIAG_TIMER_INST_TEST_SLOW
 The Timer functional 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 slow is being called.
 */
#define DIAG_TIMER_INST_TEST_SLOW  2U

/**
 @def DIAG_TIMER_FN_CALL_INST_DELAY
 The two function calls that are used before and after the execution of block of instructions also need to 
 be taken into consideration. This macro accounts for that delay
 */
#define DIAG_TIMER_FN_CALL_INST_DELAY  4U

/*Macros for TEST MODE SCENARIOS*/
#define DIAG_TIMER_TEST_NORMAL              0x91U
#define DIAG_TIMER_TEST_DISABLE_TIMER       0x92U
#define DIAG_TIMER_TEST_OVERFLOW            0x94U             
#define DIAG_TIMER_TEST_WORKING_FAST        0x95U
#define DIAG_TIMER_TEST_WORKING_SLOW        0x96U

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

/*Maximum count value*/
#define DIAG_TIMER_MAX_COUNTS              0xFFFFFFFFUL

//Variables for storing and restoring the timer module registers
static uint32_t storeFunctionalPrescalerValue = 0x0000U;
static uint32_t storeFunctionalPeriodValue = 0x0000U;
static uint32_t storeFunctionalTimerValue = 0x0000U;
static uint32_t storeFunctionalTimerInterrupt = 0x0000U;

static uint32_t testModeFunctional = DIAG_TIMER_TEST_NORMAL;/*Normal Mode*/
static DIAG_TIMER_FUNCTIONAL_RESULTS diagTimerResults;

static void DIAG_TIMER_FunctionalStoreValues(DIAG_UTILITY_TIMER_INSTANCE tmrId)
{    
    //Store the values before executing the diagnostics
    DIAG_UTIL_TIMER_DisableTimer(tmrId);
    storeFunctionalPrescalerValue = DIAG_UTIL_TIMER_GetPrescaler(tmrId);
    storeFunctionalPeriodValue = DIAG_UTIL_TIMER_GetPeriodCount(tmrId);
    storeFunctionalTimerValue = DIAG_UTIL_TIMER_GetTMRValue(tmrId);
    storeFunctionalTimerInterrupt = DIAG_UTIL_TIMER_GetInterruptEnableStatus(tmrId);
}

static void DIAG_TIMER_FunctionalRestoreValues(DIAG_UTILITY_TIMER_INSTANCE tmrId)
{   
    //Restore the values after the diagnostics is executed
    DIAG_UTIL_TIMER_DisableTimer(tmrId);
    (void)DIAG_UTIL_TIMER_SetPrescaler(tmrId, storeFunctionalPrescalerValue);
    (void)DIAG_UTIL_TIMER_SetPeriodCount(tmrId, storeFunctionalPeriodValue);
    (void)DIAG_UTIL_TIMER_SetTMRValue(tmrId, storeFunctionalTimerValue);
    if(storeFunctionalTimerInterrupt == 1U)
    {
        DIAG_UTIL_TIMER_EnableInterrupt(tmrId);
    }
}

static void DIAG_TIMER_FunctionalSetValues(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);
    DIAG_UTIL_TIMER_DisableInterrupt(tmrId);
}

static void DIAG_TIMER_SetResults(DIAG_TIMER_FUNCTIONAL_RESULTS resultScenarios)
{
    diagTimerResults = resultScenarios;
}

DIAG_TIMER_FUNCTIONAL_RESULTS DIAG_TIMER_GetResults(void)
{
    return diagTimerResults;
}

uint32_t DIAG_TIMER_FunctionalTest(DIAG_UTILITY_TIMER_INSTANCE tmrId,
									uint32_t tolerance)
{
    uint32_t status = DIAG_FAIL;
    uint32_t totalInstructionCount;
    uint32_t functionCallDelay;    	
    uint32_t startCount;
    uint32_t endCount;
    uint32_t diffCount;
    uint32_t tmrCountMax;
    uint32_t tmrCountMin;
    uint32_t tmrTestCounts = DIAG_TIMER_TEST_COUNTS;    
    volatile uint32_t numberOfInstructions = DIAG_TIMER_INST_NORMAL;
    
    /*Set the results to none before the analysis*/
	/*LDRA_EXCLUDE 151 D: <justification start> This variable is set with a known value before test, violation ignored <justification end> */
    DIAG_TIMER_SetResults(DIAG_TIMER_RESULT_NONE); 
    
    
    if((uint32_t)tmrId < (uint32_t)DIAG_UTILITY_TIMER_TOTAL)
	{ 
        
        /*Since the clock is configured in the range of MHZ, the instruction cycle will be in the range of 10s of
          nano seconds. For the block of instructions executed, check for the TMR1 value, before the instructions
          are executed and after the instructions are executed. The difference between the two TMR1 values
          should be within  the tolerance band*/
        /*There are two function calls between assigning the value to startCount and endCount. This also has
          to be accounted for*/

        functionCallDelay = (uint32_t)(DIAG_TIMER_FN_CALL_INST_DELAY * 2U);
        totalInstructionCount = (uint32_t)(DIAG_TIMER_INST_NORMAL + functionCallDelay);

        /* With the calculated timer hit count and the tolerance, we get the
        Maximum and Minimum timer count hits */
        
        if(tolerance > (DIAG_TIMER_MAX_COUNTS - totalInstructionCount))
        {
            tmrCountMax = DIAG_TIMER_MAX_COUNTS;
        }
        else
        {
            tmrCountMax = totalInstructionCount + tolerance;
        }
        
        if(tolerance > totalInstructionCount)
        {
            tmrCountMin = 0U;
        }
        else
        {
            tmrCountMin = totalInstructionCount - tolerance;
        }
        
        //Store the values before executing the diagnostics
        DIAG_TIMER_FunctionalStoreValues(tmrId);

        //Set the predefined values required for the diagnostics
        DIAG_TIMER_FunctionalSetValues(tmrId);

        //Select the parameters based on normal mode or test mode
        switch(testModeFunctional)
        {
            case DIAG_TIMER_TEST_NORMAL:/*Normal Mode*/
                            DIAG_UTIL_TIMER_EnableTimer(tmrId);
                            break;
            case DIAG_TIMER_TEST_DISABLE_TIMER:/*Test Disable Timer Mode*/
                            DIAG_UTIL_TIMER_DisableTimer(tmrId);
                            break;
            case DIAG_TIMER_TEST_OVERFLOW:/*Test Timer Overflow Mode*/
                            (void)DIAG_UTIL_TIMER_SetTMRValue(tmrId, tmrTestCounts);
                            DIAG_UTIL_TIMER_EnableTimer(tmrId);
                            break;                             
            case DIAG_TIMER_TEST_WORKING_FAST:/*Test Timer Fast Mode*/
                            numberOfInstructions = DIAG_TIMER_INST_TEST_FAST;
                            DIAG_UTIL_TIMER_EnableTimer(tmrId);
                            break;
            case DIAG_TIMER_TEST_WORKING_SLOW:/*Test Timer Slow Mode*/
                            numberOfInstructions = DIAG_TIMER_INST_TEST_SLOW;
                            DIAG_UTIL_TIMER_EnableTimer(tmrId);
                            break;                           
            default:// The control should not reach here.
                            break;
        }
        
        /* Before the Nop block is run, the TMR count is read */
        startCount = DIAG_UTIL_TIMER_GetTMRValue(tmrId);    

        /* The Instruction block that allows the CPU to wait for a specified time interval is run */

        /*LDRA_EXCLUDE 496 S <justification start> This is a compiler builtin function and so 496 S is ignored <justification end> */
        __builtin_repeat_nop((numberOfInstructions * 2U));

        endCount = DIAG_UTIL_TIMER_GetTMRValue(tmrId);

        DIAG_UTIL_TIMER_DisableTimer(tmrId);

        if((endCount < startCount))   
        {
            /* Indicates an overflow of the count value */
            status = DIAG_FAIL;
        }
        else if((endCount == startCount))
        {
            /* Indicates an timer is not running */
            status = DIAG_FAIL;
        }
        /* The Interrupt count value has not overflown, and hence we can proceed */
        else
        {
            /* We check if the Timer hit count is within the tolerance range */
            diffCount = endCount - startCount;

            if(diffCount > tmrCountMax)
            {   
                /* Indicates timer is running faster than expected */
                DIAG_TIMER_SetResults(DIAG_TIMER_RESULT_FAST);
                status = DIAG_FAIL;
            }
            else if(diffCount < tmrCountMin)
            {
                /* Indicates timer is running slower than expected */
                DIAG_TIMER_SetResults(DIAG_TIMER_RESULT_SLOW);
                status = DIAG_FAIL;
            }
            else
            {
                DIAG_TIMER_SetResults(DIAG_TIMER_RESULT_NORMAL);   
                status = DIAG_PASS;
            }
        }
        //Restore the values after the diagnostics is executed
        DIAG_TIMER_FunctionalRestoreValues(tmrId);
    }
    else
    {
        status = DIAG_INVALID_PARAM;
    }
        
    return status;
}

void DIAG_TIMER_SetTestModeFunctional(uint32_t setTestMode)
{
    testModeFunctional = setTestMode;
}
