/******************************************************************************
 * @file     lin.c
 * @brief    LIN firmware functions.
 * @version  V4.0.0
 * @date     30-July-2026
 *
 * @note
 * Copyright (C) 2022 Spintrol Electronic Technology (Shanghai) Co., Ltd.. All rights reserved.
 *
 * @attention
 * THIS SOFTWARE JUST PROVIDES CUSTOMERS WITH CODING INFORMATION REGARDING
 * THEIR PRODUCTS, WHICH AIMS AT SAVING TIME FOR THEM. SPINTROL SHALL NOT BE
 * LIABLE FOR THE USE OF THE SOFTWARE. SPINTROL DOES NOT GUARANTEE THE
 * CORRECTNESS OF THIS SOFTWARE AND RESERVES THE RIGHT TO MODIFY THE SOFTWARE
 * WITHOUT NOTIFICATION.
 *
 ******************************************************************************/

#include "lin.h"




/******************************************************************************
 * @brief      Read a byte data from lin bus
 *
 * @param[in]  pu8Data   :  Readed data
 * @param[in]  u32Timeout:  Time out
 *
 * @return     none
 *
 ******************************************************************************/
static uint32_t LIN_ReadByte(uint8_t *pu8Data, uint32_t u32Timeout)
{
    volatile uint32_t i = u32Timeout;

    while (i-- > 0)
    {
        /* Receive data available */
        if (UART_IsRxNotEmpty(UART))
        {
            *pu8Data = UART_ReadByte(UART);

            return LIN_OK;
        }
    }

    return LIN_TIMEOUT;
}




/******************************************************************************
 * @brief      Check feedback data from lin bus when sending a byte data finished
 *
 * @param[in]  u8CheckData:  Data to be check
 * @param[in]  u32Timeout :  Time out
 *
 * @return     none
 *
 ******************************************************************************/
static uint32_t LIN_CheckFeedbackByte(uint8_t u8CheckData, uint32_t u32Timeout)
{
    uint32_t status;
    uint8_t u8Data;

    /* Receive a byte data */
    status = LIN_ReadByte(&u8Data, u32Timeout);
    if (LIN_OK != status)
    {
        return LIN_BUS_OFFLINE;
    }

    /* Check feedback byte */
    if (u8CheckData != u8Data)
    {
        return LIN_BUS_ERROR;
    }

    return LIN_OK;
}




/******************************************************************************
 * @brief      Write a byte data to lin bus
 *
 * @param[in]  u8Data    :  Data to be sent
 * @param[in]  u32Timeout:  Time out
 *
 * @return     none
 *
 ******************************************************************************/
static uint32_t LIN_WriteByte(const uint8_t u8Data, uint32_t u32Timeout)
{
    uint32_t status;
    volatile uint32_t i = 0;

    UART_WriteByte(UART, u8Data);

    /* Wait transmit done */
    while (UART_IsTxDone(UART) == 0)
    {
        if (i++ > u32Timeout)
        {
            return LIN_TIMEOUT;
        }
    }

    /* Check bus feedback data */
    status = LIN_CheckFeedbackByte(u8Data, u32Timeout);

    return status;
}




/******************************************************************************
 * @brief      Calculate lin frame checksum
 *
 * @param[in]  u8ID   :  Identifier
 * @param[in]  pu8Data:  Data
 * @param[in]  u8Len  :  Data length
 *
 * @return     none
 *
 ******************************************************************************/
static uint8_t LIN_CalChecksum(uint8_t u8ID, uint8_t *pu8Data, uint8_t u8Len)
{
    uint16_t i;
    uint16_t u16Chksum = 0;

    uint8_t u8Parity6 = 0x1 & ((u8ID >> 0) ^ (u8ID >> 1) ^ (u8ID >> 2) ^ (u8ID >> 4));
    uint8_t u8Parity7 = 0x1 & ~((u8ID >> 1) ^ (u8ID >> 3) ^ (u8ID >> 4) ^ (u8ID >> 5));

    /* Frame Protected ID */
    uint8_t u8PID = u8ID | (u8Parity6 << 6) | (u8Parity7 << 7);


    /* Checksum calculation over the data bytes */
    for (i = 0; i < u8Len; i++)
    {
        u16Chksum += pu8Data[i];

        if (u16Chksum > 0xFF)
        {
            u16Chksum -= 0xFF;
        }
    }

    /* Enhanced checksum for communication with LIN 2.x slave nodes */
#if( LIN_VERSION == 0x220 )

    if ((u8ID != LIN_ID_MASTER_REQ) && (u8ID != LIN_ID_SLAVE_ACK))
    {
        u16Chksum += u8PID;

        if (u16Chksum > 0xFF)
        {
            u16Chksum -= 0xFF;
        }
    }

#endif

    return (uint8_t)(~u16Chksum & 0xFF);
}




/******************************************************************************
 * @brief      Send break signal (LIN master)
 *
 * @param[in]  u32Timeout:  Time out
 *
 * @return     none
 *
 ******************************************************************************/
static uint32_t LIN_SendBreak(uint32_t u32Baudrate, uint32_t u32Timeout)
{
    uint32_t status;

    /* Set TX = 0 */
    UART_EnableSetBreak(UART);
    /* Wait for 15 bit-time to generate break signal */
    Delay_Us(15 * 1000 * 1000 / u32Baudrate);

    /* Set TX = 1 */
    UART_DisableSetBreak(UART);
    /* Wait for 2 bit-time to generate break delimiter */
    Delay_Us(2 * 1000 * 1000 / u32Baudrate);


    /* Check bus feedback data */
    status = LIN_CheckFeedbackByte(0x00, u32Timeout);
    if (LIN_OK != status)
    {
        return status | LIN_MASTER_SYNC_FIELD_ERROR;
    }

    return LIN_OK;
}



/******************************************************************************
 * @brief      Wait break signal detected (LIN slave)
 *
 * @param[in]  u32Timeout:  Time out
 *
 * @return     none
 *
 ******************************************************************************/
static uint32_t LIN_WaitBreak(uint32_t u32Timeout)
{
    uint32_t status;
    volatile uint32_t i = 0;

    /* Wait break signal detected */
    while (UART_IsRxBreak(UART) == 0)
    {
        if (i++ > u32Timeout)
        {
            return LIN_TIMEOUT | LIN_SLAVE_SYNC_FIELD_ERROR;
        }
    }

    /* Check received data */
    status = LIN_CheckFeedbackByte(0x00, u32Timeout);
    if (LIN_OK != status)
    {
        return status | LIN_SLAVE_SYNC_FIELD_ERROR;
    }

    return LIN_OK;
}




/******************************************************************************
 * @brief      Send frame header (LIN master)
 *
 * @param[in]  u32Baudrate:  Baud rate
 * @param[in]  u8ID       :  Identifier
 * @param[in]  u32Timeout :  Time out
 *
 * @return     none
 *
 ******************************************************************************/
uint32_t LIN_SendHeader(uint32_t u32Baudrate, uint8_t u8ID, uint32_t u32Timeout)
{
    uint32_t status;

    uint8_t u8Parity6 = 0x1 & ((u8ID >> 0) ^ (u8ID >> 1) ^ (u8ID >> 2) ^ (u8ID >> 4));
    uint8_t u8Parity7 = 0x1 & ~((u8ID >> 1) ^ (u8ID >> 3) ^ (u8ID >> 4) ^ (u8ID >> 5));

    /* Frame Protected ID */
    uint8_t u8PID = u8ID | (u8Parity6 << 6) | (u8Parity7 << 7);

    /* Send break field */
    status = LIN_SendBreak(u32Baudrate, u32Timeout);
    if (LIN_OK != status)
    {
        return status;
    }


    /* Send Sync field: 0x55 */
    status = LIN_WriteByte(0x55, u32Timeout);
    if (LIN_OK != status)
    {
        return status | LIN_MASTER_SYNC_FIELD_ERROR;
    }

    Delay_Ms(INTER_BYTE_SPACE_TIME_MS);


    /* Send Protected ID field */
    status = LIN_WriteByte(u8PID, u32Timeout);
    if (LIN_OK != status)
    {
        return status | LIN_MASTER_SYNC_FIELD_ERROR;
    }

    return LIN_OK;
}




/******************************************************************************
 * @brief      Wait and Receive frame header (LIN slave)
 *
 * @param[in]  u8ID      :  Received Identifier
 * @param[in]  u32Timeout:  Time out
 *
 * @return     none
 *
 ******************************************************************************/
uint32_t LIN_WaitHeader(uint8_t *u8ID, uint32_t u32Timeout)
{
    uint32_t status;

    uint8_t u8Data;

    uint8_t u8Parity6 = 0;
    uint8_t u8Parity7 = 0;


    /* Wait break signal detected */
    status = LIN_WaitBreak(u32Timeout);
    if (LIN_OK != status)
    {
        return status;
    }


    /* Receive Sync field: 0x55 */
    status = LIN_ReadByte(&u8Data, u32Timeout);
    if (LIN_OK != status)
    {
        return status | LIN_SLAVE_SYNC_FIELD_ERROR;
    }

    /* Check data */
    if (0x55 != u8Data)
    {
        return LIN_SLAVE_SYNC_FIELD_ERROR;
    }


    /* Receive Protected ID field */
    status = LIN_ReadByte(&u8Data, u32Timeout);
    if (LIN_OK != status)
    {
        return status | LIN_SLAVE_SYNC_FIELD_ERROR;
    }

    /* Check Protected ID */
    *u8ID     = u8Data & 0x3F;
    u8Parity6 = 0x1 & ((*u8ID >> 0) ^ (*u8ID >> 1) ^ (*u8ID >> 2) ^ (*u8ID >> 4)) ;
    u8Parity7 = 0x1 & ~((*u8ID >> 1) ^ (*u8ID >> 3) ^ (*u8ID >> 4) ^ (*u8ID >> 5)) ;

    if ((u8Parity6 != ((u8Data >> 6) & 0x1))
            || (u8Parity7 != ((u8Data >> 7) & 0x1))
       )
    {
        return LIN_ID_ERROR | LIN_SLAVE_SYNC_FIELD_ERROR;
    }

    return LIN_OK;
}




/******************************************************************************
 * @brief      Send frame response data
 *
 * @param[in]  u8ID      :  Identifier
 * @param[in]  pu8Data   :  Data
 * @param[in]  u8Len     :  Data length
 * @param[in]  u32Timeout:  Time out
 *
 * @return     none
 *
 ******************************************************************************/
uint32_t LIN_SendData(uint8_t u8ID, uint8_t *pu8Data, uint8_t u8Len, uint32_t u32Timeout)
{
    uint32_t status;
    uint32_t i;

    uint8_t u8Chksum = LIN_CalChecksum(u8ID, pu8Data, u8Len);


    /* Send data */
    for (i = 0; i < u8Len; i++)
    {
        /* Send a byte */
        status = LIN_WriteByte(pu8Data[i], u32Timeout);
        if (LIN_OK != status)
        {
            return status | LIN_DATA_FIELD_SEND_DATA_FAILED;
        }

        Delay_Ms(INTER_BYTE_SPACE_TIME_MS);
    }

    /* Send checksum */
    status = LIN_WriteByte(u8Chksum, u32Timeout);
    if (LIN_OK != status)
    {
        return status | LIN_DATA_FIELD_SEND_DATA_FAILED;
    }

    return LIN_OK;
}




/******************************************************************************
 * @brief      Receive frame response data
 *
 * @param[in]  u8ID      :  Identifier
 * @param[in]  pu8Data   :  Data
 * @param[in]  u8Len     :  Data length
 * @param[in]  u32Timeout:  Time out
 *
 * @return     none
 *
 ******************************************************************************/
uint32_t LIN_ReceiveData(uint8_t u8ID, uint8_t *pu8Data, uint8_t u8Len, uint32_t u32Timeout)
{
    uint32_t status;
    uint32_t i;
    uint8_t  u8Chksum;
    uint8_t  u8Temp;

    /* Receive data */
    for (i = 0; i < u8Len; i++)
    {
        status = LIN_ReadByte(&pu8Data[i], u32Timeout);
        if (LIN_OK != status)
        {
            return status | LIN_DATA_FIELD_RECEIVE_DATA_FAILED;
        }
    }

    /* Receive checksum */
    status = LIN_ReadByte(&u8Chksum, u32Timeout);
    if (LIN_OK != status)
    {
        return status | LIN_DATA_FIELD_RECEIVE_PARITY_FAILED;
    }

    /* Check checksum */
    u8Temp = LIN_CalChecksum(u8ID, pu8Data, u8Len);
    if (u8Chksum != u8Temp)
    {
        return LIN_DATA_FIELD_RECEIVE_PARITY_ERROR;
    }

    return LIN_OK;
}




/******************************************************************************
 * @brief      LIN master control
 *
 * @param[in]  RnW        :  LIN operation
 * @param[in]  u32Baudrate:  Baud rate
 * @param[in]  u8ID       :  Identifier
 * @param[in]  pu8Data    :  Data
 * @param[in]  u8Len      :  Data length
 * @param[in]  u32Timeout :  Time out
 *
 * @return     none
 *
 ******************************************************************************/
uint32_t LIN_MasterControl(LIN_ReadWriteEnum RnW, uint32_t u32Baudrate, uint8_t u8ID, uint8_t *pu8Data, uint8_t u8Len, uint32_t u32Timeout)
{
    uint32_t status;

    /* Send frame header */
    status = LIN_SendHeader(u32Baudrate, u8ID, u32Timeout);
    if (LIN_OK != status)
    {
        return status;
    }

    /* Send or Receivce response data */
    switch (RnW)
    {
        case LIN_READ:
            /* Receive response data */
            status = LIN_ReceiveData(u8ID, pu8Data, u8Len, u32Timeout);
            if (LIN_OK != status)
            {
                return status;
            }

            break;

        case LIN_WRITE:
            /* Send response data */
            Delay_Ms(RESPONSE_SPACE_TIME_MS);

            status = LIN_SendData(u8ID, pu8Data, u8Len, u32Timeout);
            if (LIN_OK != status)
            {
                return status;
            }

            break;
    }

    Delay_Ms(INTER_FRAME_SPACE_TIME_MS);

    return LIN_OK;
}




/******************************************************************************
 * @brief      LIN slave control
 *
 * @param[in]  RnW       :  LIN operation
 * @param[in]  pu8ID     :  Identifier
 * @param[in]  pu8Data   :  Data
 * @param[in]  u8Len     :  Data length
 * @param[in]  u32Timeout:  Time out
 *
 * @return     none
 *
 ******************************************************************************/
uint32_t LIN_SlaveControl(LIN_ReadWriteEnum RnW, uint8_t *pu8ID, uint8_t *pu8Data, uint8_t u8Len, uint32_t u32Timeout)
{
    uint32_t status;

    /* Wait break detected and receive frame header */
    status = LIN_WaitHeader(pu8ID, u32Timeout);
    if (LIN_OK != status)
    {
        return status;
    }

    /* Check ID if supported */
    /* To Do */

    /* Send or Receivce response data */
    switch (RnW)
    {
        case LIN_READ:
            /* Receive response data */
            status = LIN_ReceiveData(*pu8ID, pu8Data, u8Len, u32Timeout);
            if (LIN_OK != status)
            {
                return status;
            }

            break;

        case LIN_WRITE:
            /* Send response data */
            Delay_Ms(RESPONSE_SPACE_TIME_MS);

            status = LIN_SendData(*pu8ID, pu8Data, u8Len, u32Timeout);
            if (LIN_OK != status)
            {
                return status;
            }

            break;
    }

    return LIN_OK;
}


/******************* Copyright (C) 2022 Spintrol Electronic Technology (Shanghai) Co., Ltd. ***** END OF FILE ****/
