/* USER CODE BEGIN Header */
/* you can remove the copyright */
/*
 *  Copyright 2020-2024 Yuntu Microelectronics co.,ltd
 *  All rights reserved.
 * 
 *  YUNTU Confidential. This software is owned or controlled by YUNTU and may only be
 *  used strictly in accordance with the applicable license terms. By expressly
 *  accepting such terms or by downloading, installing, activating and/or otherwise
 *  using the software, you are agreeing that you have read, and that you agree to
 *  comply with and are bound by, such license terms. If you do not agree to be
 *  bound by the applicable license terms, then you may not retain, install,
 *  activate or otherwise use the software. The production use license in
 *  Section 2.3 is expressly granted for this software.
 * 
 * @file lin_sync_api.c
 * @brief 
 * 
 */

 #include "lin_sync_api.h"
#include "lin_driver.h"
 #include "uart_hw_access.h"


/********************************************************************* Macro *****************************************************/


/********************************************************************* Variable *****************************************************/
static clock_names_t s_uartClock[] = UART_CLOCK_NAMES;
UART_Type* g_uartBasePtr[] = UART_BASE_PTRS;

/*! @brief Table to save UART state structure pointers */
extern lin_state_t * g_linStatePtr[];
lin_sync_t g_linSync;

/********************************************************************* Function *****************************************************/
static inline uint32_t LinSync_GetDivideQuotient(uint32_t dividend, uint32_t divisor);
static uint32_t LinSync_GetTimeBetweenTwoEdge(void);
static status_t LinSync_SetBaudRate(lin_sync_t *linSync, uint32_t desiredBaudRate);

status_t LinSync_AutoBaudCapture(lin_sync_t *linSync)
{
    status_t retVal = STATUS_BUSY;
    /* Get the current LIN state of this UART instance. */
    lin_state_t * linCurrentState = g_linStatePtr[linSync->uartInstance];

    if (linSync->isSyncing || linCurrentState->currentNodeState == LIN_NODE_STATE_RECV_SYNC)
    {
        /* Get two bits time length */
        linSync->bitTimeLength[linSync->edgeCount] = LinSync_GetTimeBetweenTwoEdge();
        linSync->edgeCount++;
        if (linSync->edgeCount >= sizeof(linSync->bitTimeLength) / sizeof(linSync->bitTimeLength[0]))
        {
            linSync->averageBitTimeLength = 0U;
            for (uint32_t i = 2; i < sizeof(linSync->bitTimeLength) / sizeof(linSync->bitTimeLength[0]); i++)
            {
                linSync->averageBitTimeLength += linSync->bitTimeLength[i];
            }
            linSync->averageBitTimeLength = linSync->averageBitTimeLength / (sizeof(linSync->bitTimeLength) / sizeof(linSync->bitTimeLength[0]) - 2);
            linSync->currentBaudrateHz = LinSync_GetDivideQuotient(LIN_SYNC_TIMER_CLOCK_HZ, linSync->averageBitTimeLength);
            if((linSync->currentBaudrateHz > (linSync->nominalBaudrateHz + linSync->nominalBaudrateHz*12/100)) || (linSync->currentBaudrateHz < (linSync->nominalBaudrateHz - linSync->nominalBaudrateHz*12/100)))
            {
                linCurrentState->currentEventId = LIN_SYNC_ERROR;
            }else{
                LinSync_SetBaudRate(linSync, linSync->currentBaudrateHz);
                /* Update current state and current event */
                linCurrentState->currentNodeState = LIN_NODE_STATE_RECV_PID;
                linCurrentState->currentEventId = LIN_SYNC_OK;
            }

            retVal = STATUS_SUCCESS;
            linSync->edgeCount = 0U;
            linSync->isSyncing = false;

            /* Callback function to handle this event */
            if (linCurrentState->Callback != NULL)
            {
                linCurrentState->Callback(linSync->uartInstance, linCurrentState);
            }

            if(linCurrentState->currentEventId == LIN_SYNC_ERROR)
            {
                LIN_DRV_GotoIdleState(linSync->uartInstance);
            }
            
        }
    }else{
        retVal = STATUS_ERROR;
        linSync->edgeCount = 0U;
        linSync->isSyncing = false;
    }

    return retVal;
}


void LinSync_Init(lin_sync_t *linSync, uint32_t nominalBaudrateHz)
{
    UART_Type * base = g_uartBasePtr[linSync->uartInstance];
    linSync->isSyncing = false;
    linSync->edgeCount = 0U;

    for (uint32_t i = 0; i < 10U; i++)
    {
        linSync->bitTimeLength[i] = 0U;
    }
    linSync->nominalBaudrateHz = nominalBaudrateHz;
    linSync->nominalOsr = UART_GetOversamplingRatio(base);
    linSync->nominalSbr = UART_GetBaudRateDivisor(base);
}

static uint32_t LinSync_GetTimeBetweenTwoEdge(void)
{
    uint32_t returnValue = 0;

    returnValue = g_linSync.previousCounterValue - g_linSync.capturedValue;
    g_linSync.previousCounterValue = g_linSync.capturedValue;

    return returnValue;
}

static status_t LinSync_SetBaudRate(lin_sync_t *linSync, uint32_t desiredBaudRate)
{
    DEV_ASSERT(linSync->uartInstance < UART_INSTANCE_COUNT);

    uint16_t sbr, sbrTemp, i;
    uint32_t osr, tempDiff, calculatedBaud, baudDiff, maxOsr;
    uint32_t uartSourceClock;
    clock_names_t instanceClkName = s_uartClock[linSync->uartInstance];
    UART_Type * base = g_uartBasePtr[linSync->uartInstance];

    /* Get the UART clock as configured in the clock manager */
    (void)CLOCK_SYS_GetFreq(instanceClkName, &uartSourceClock);

    /* Check if current instance is clock gated off. */
    DEV_ASSERT(uartSourceClock > 0U);
    /* Check if the desired baud rate can be configured with the current protocol clock. */
    DEV_ASSERT(uartSourceClock >= (desiredBaudRate * 4U));

    /* This uart instantiation uses a slightly different baud rate calculation
     * The idea is to use the best OSR (over-sampling rate) possible
     * Note, osr is typically hard-set to 16 in other uart instantiations
     * First calculate the baud rate using the minimum OSR possible (4) */
    osr = 4;
    sbr = LinSync_GetDivideQuotient(uartSourceClock, desiredBaudRate * osr);

    calculatedBaud = LinSync_GetDivideQuotient(uartSourceClock, (osr * sbr));
    if (calculatedBaud > desiredBaudRate)
    {
        baudDiff = calculatedBaud - desiredBaudRate;
    }
    else
    {
        baudDiff = desiredBaudRate - calculatedBaud;
    }
    /* find maximum osr */
    maxOsr = LinSync_GetDivideQuotient(uartSourceClock, desiredBaudRate);  
    if (maxOsr > 32U)
    {
        maxOsr = 32U;
    }
    /* loop to find the best osr value possible, one that generates minimum baudDiff
     * iterate through the rest of the supported values of osr */
    if (maxOsr >= 5U)
    {
        for (i = 5U; i <= maxOsr; i++)
        {
            /* calculate the temporary sbr value   */
            sbrTemp = LinSync_GetDivideQuotient(uartSourceClock, (desiredBaudRate * i));
            /* calculate the baud rate based on the temporary osr and sbr values */
            calculatedBaud = LinSync_GetDivideQuotient(uartSourceClock, (i * sbrTemp));

            if (calculatedBaud > desiredBaudRate)
            {
                tempDiff = calculatedBaud - desiredBaudRate;
            }
            else
            {
                tempDiff = desiredBaudRate - calculatedBaud;
            }

            if (tempDiff <= baudDiff)
            {
                baudDiff = tempDiff;
                osr = i;  /* update and store the best osr value calculated */
                sbr = sbrTemp;  /* update store the best sbr value calculated */
            }
        }
    }
    /* Check if osr is between 4x and 7x oversampling.
     * If so, then "BOTHEDGE" sampling must be turned on */
    if (osr < 8U)
    {
    	UART_EnableBothEdgeSamplingCmd(base);
    }

    /* program the osr value (bit value is one less than actual value) */
    UART_SetOversamplingRatio(base, (osr - 1U));

    /* write the sbr value to the BAUD registers */
    UART_SetBaudRateDivisor(base, sbr);

    return STATUS_SUCCESS;
}

static inline uint32_t LinSync_GetDivideQuotient(uint32_t dividend, uint32_t divisor)
{
#if defined(DIVSQRT_INSTANCE_COUNT) && (DIVSQRT_INSTANCE_COUNT > 0)
    DIVSQRT->DEND = dividend;
    DIVSQRT->DSOR = divisor;
    DIVSQRT->CSR |= DIVSQRT_CSR_SRT_MASK;
    return (DIVSQRT->RES);
#else
    return (dividend / divisor);
#endif
}