/**
  ******************************************************************************
  * @file    kpm32xx_ddl_can.c
  * @author  Kiwi Software Team
  * @brief   CAN DDL module driver.
  *          This file provides firmware functions to manage the following
  *          functionalities of the Controller Area Network:
  *          + Initialization and de-initialization functions
  *          + transmint and receive functions
  *          + Interrupts management
  * @note
  *          V1.0.0, 2024/12/20.
  *
  * Copyright (c) 2024, Kiwi Instruments Co,. Ltd.
  *
  * Redistribution and use in source and binary forms, with or without modification,
  * are permitted provided that the following conditions are met:
  *
  *   1. Redistributions of source code must retain the above copyright notice,
  *      this list of conditions and the following disclaimer.
  *
  *   2. Redistributions in binary form must reproduce the above copyright notice,
  *      this list of conditions and the following disclaimer in the documentation
  *      and/or other materials provided with the distribution.
  *
  *   3. Neither the name of the copyright holder nor the names of its contributors
  *      may be used to endorse or promote products derived from this software without
  *      specific prior written permission.
  *
  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
  * THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
  * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
  * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
  * THE POSSIBILITY OF SUCH DAMAGE.
  ******************************************************************************
  */


#include "kpm32xx_ddl.h"


#ifdef DDL_CAN_MODULE_ENABLED



#define CAN_TIMEOUT_VALUE      20U


static DDL_Status_T CAN_ConfigBusTiming(CAN_Type *CANx, CAN_Init_T *pCANStruct)
{
	uint32_t canClock = 0;
	uint32_t baudratePrescaler = 0;
    DDL_Status_T ret = DDL_OK;

	ASSERT_PARAM(pCANStruct->busTime.timeSeg1 <= CAN_TIMESEG1_16TQ);
	ASSERT_PARAM(pCANStruct->busTime.timeSeg2 <= CAN_TIMESEG2_8TQ);
	ASSERT_PARAM(pCANStruct->busTime.syncJumpWidth <= CAN_SYNCJUMPWIDTH_4TQ);

	/* CAN IP clock is the same as system clock */
	canClock = DDL_RCC_GetSysClockFreq() / 2;
	baudratePrescaler = (canClock / (2 * pCANStruct->baudrate * ((uint32_t)(pCANStruct->busTime.timeSeg1) + (uint32_t)(pCANStruct->busTime.timeSeg2) + 3))) - 1;
	if (baudratePrescaler > CAN_MAX_BRP)
	{
		return DDL_INVPARAM;
	}

	/* Set bus timing parameters */
	SET_BITMASK(CANx->BUSTIM, CAN_BUSTIM_BRP_Mask, baudratePrescaler << CAN_BUSTIM_BRP_Pos);
	SET_BITMASK(CANx->BUSTIM, CAN_BUSTIM_TSEG1_Mask, pCANStruct->busTime.timeSeg1 << CAN_BUSTIM_TSEG1_Pos);
	SET_BITMASK(CANx->BUSTIM, CAN_BUSTIM_TSEG2_Mask, pCANStruct->busTime.timeSeg2 << CAN_BUSTIM_TSEG2_Pos);
	SET_BITMASK(CANx->BUSTIM, CAN_BUSTIM_SJW_Mask, pCANStruct->busTime.syncJumpWidth << CAN_BUSTIM_SJW_Pos);
	SET_BITMASK(CANx->BUSTIM, CAN_BUSTIM_SAMPLING_Mask, pCANStruct->busTime.sample << CAN_BUSTIM_SAMPLING_Pos);

    return ret;
}


/**
  * @brief  Configure the filter to receive message.
  * @param  CANx         CAN instance.
  * @param  filter       store the filter's configuration
  * @param  filterIndex  specify the index of filter to use
  * @retval None
  * @note   Only can configure filter in INIT mode.
  */
void DDL_CAN_ConfigFilter(CAN_Type *CANx, CAN_Filter_T *filter, uint8_t filterIndex)
{
    ASSERT_PARAM(filterIndex < CAN_MAX_FILTER_NUM);

	if (filterIndex == 0)
	{
		CANx->ACPTCODE0 = filter->acceptCode;
		CANx->ACPTMASK0 = filter->maskCode;
	}
	else
	{
		CANx->ACPTCODE1 = filter->acceptCode;
		CANx->ACPTMASK1 = filter->maskCode;
	}
}


/**
  * @brief  Check which bit lost the arbitration.
  * @param  CANx   CAN instance.
  * @retval RTR or the index of identifier
  */
uint8_t DDL_CAN_GetArbitLostBit(CAN_Type *CANx)
{
	uint8_t i = 0;
	uint32_t arbitLostReg = CANx->ARBITLOST;

	for (i = 0; i < 32; i++)
	{
		if ((arbitLostReg >> i) & 0x1)
		{
			break;
		}
	}

	return i;
}


/**
  * @brief  Get the error information.
  * @param  CANx     CAN instance.
  * @param  errInfo  pointer to a CAN_ErrorInfo_T structure that contains
  *                  the error information.
  * @retval None
  */
void DDL_CAN_GetErrInfor(CAN_Type *CANx, CAN_ErrorInfo_T *errInfo)
{
	errInfo->rxErrCnt = (CANx->ERRCNT & CAN_ERRCNT_REC_Mask) >> CAN_ERRCNT_REC_Pos;
	errInfo->txErrCnt = (CANx->ERRCNT & CAN_ERRCNT_TEC_Mask) >> CAN_ERRCNT_TEC_Pos;

	errInfo->errType = (CAN_ErrCode_T)((CANx->ERRINFO & CAN_ERRINFO_ERRCODE_Mask) >> CAN_ERRINFO_ERRCODE_Pos);
	errInfo->errDir = (CAN_ErrorDir_T)((CANx->ERRINFO & CAN_ERRINFO_ERRDIR_Mask) >> CAN_ERRINFO_ERRDIR_Pos);
	errInfo->errSegment = (CAN_ErrSegment_T)((CANx->ERRINFO & CAN_ERRINFO_SEGCODE_Mask) >> CAN_ERRINFO_SEGCODE_Pos);
}


/**
  * @brief  Make specified CAN active.
  * @param  CANx        CAN instance.
  * @retval None.
  */
void DDL_CAN_Instance_Active(CAN_Type *CANx)
{
	__DDL_RCC_CAN_ACTIVE();
	__DDL_RCC_CAN_CLK_RELEASE();
}


/**
  * @brief  Make specified CAN deactive.
  * @param  CANx        CAN instance.
  * @retval None.
  */
void DDL_CAN_Instance_Deactive(CAN_Type *CANx)
{
	__DDL_RCC_CAN_DEACTIVE();
	__DDL_RCC_CAN_CLK_HOLD();
}


/**
  * @brief  Configure the elements of structure CAN_Init_T to default values.
  * @param  pCANStruct Pointer to a CAN_Init_T structure that contains
  *         the configuration information for the given CAN module.
  * @retval None
  */
void DDL_CAN_StructInit(CAN_Init_T *pCANStruct)
{
	pCANStruct->mode = CAN_MODE_NORMAL;
	pCANStruct->outputMode = CAN_OPMODE_CLOCK;
	pCANStruct->baudrate = CAN_BAUDRATE_500KBPS;
	pCANStruct->errWarnThreshold = 96;

	pCANStruct->busTime.sample = CAN_SAMPLE_ONE;
	pCANStruct->busTime.syncJumpWidth = CAN_SYNCJUMPWIDTH_1TQ;
	pCANStruct->busTime.timeSeg1 = CAN_TIMESEG1_16TQ;
	pCANStruct->busTime.timeSeg2 = CAN_TIMESEG2_8TQ;
}


/**
  * @brief  Initializes the CAN peripheral.
  * @param  CANx   CAN instance.
  * @param  pCANStruct Pointer to a CAN_Init_T structure that contains
  *         the configuration information for the given CAN module.
  * @retval DDL status
  */
DDL_Status_T DDL_CAN_Init(CAN_Type *CANx, CAN_Init_T *pCANStruct)
{
	DDL_Status_T ret = DDL_OK;

	ASSERT_PARAM(pCANStruct->errWarnThreshold <= CAN_MAX_ERRWARNTHR);

	/* Request initialisation */
	SET_BITMASK(CANx->CFG, CAN_CFG_WORKMODE_Mask, CAN_CFG_WORKMODE_INIT);

	/* Configure bus timing */
	ret = CAN_ConfigBusTiming(CANx, pCANStruct);
	if (ret != DDL_OK)
	{
		return ret;
	}

	WRITE_REG(CANx->ERRWARNTHR, pCANStruct->errWarnThreshold);
	/* Set output control mode */
	SET_BITMASK(CANx->OUTCTRL, CAN_OUTCTRL_OCMODE_Mask, pCANStruct->outputMode);
	/* Set transmit and receive error counter */
	WRITE_REG(CANx->ERRCNT, (0x00 << CAN_ERRCNT_REC_Pos) | (0x00 << CAN_ERRCNT_TEC_Pos));

	return DDL_OK;
}


/**
  * @brief  Start the CAN module.
  * @param  CANx      CAN instance.
  * @param  pCANStruct Pointer to a CAN_Init_T structure that contains
  *         the configuration information for the given CAN module.
  * @retval None
  */
void DDL_CAN_Start(CAN_Type *CANx, CAN_Init_T *pCANStruct)
{
	/* Set work mode */
	SET_BITMASK(CANx->CFG, CAN_CFG_WORKMODE_Mask, pCANStruct->mode);
}


/**
  * @brief  Enable CAN interrupt with specified type.
  * @param  CANx      CAN instance.
  * @param  intrMask  CAN interrupt type.
  * @retval None.
  */
void DDL_CAN_IntEnable(CAN_Type *CANx, uint32_t intrMask)
{
	__DDL_CAN_ENABLE_IT(CANx, intrMask);
}


/**
  * @brief  Disable CAN interrupt with specified type.
  * @param  CANx      CAN instance.
  * @param  intrMask  CAN interrupt type.
  * @retval None.
  */
void DDL_CAN_IntDisable(CAN_Type *CANx, uint32_t intrMask)
{
	__DDL_CAN_DISABLE_IT(CANx, intrMask);
}


/**
  * @brief  Request to enter the sleep mode (low power).
  * @param  CANx   CAN instance.
  * @retval None.
  * @note   Only can enter into sleep mode in normal mode.
  */
void DDL_CAN_RequestSleep(CAN_Type *CANx)
{
	if ((CANx->CFG & CAN_CFG_WORKMODE_Mask) != CAN_CFG_WORKMODE_NORMAL)
	{
		/* Set work mode */
		SET_BITMASK(CANx->CFG, CAN_CFG_WORKMODE_Mask, CAN_CFG_WORKMODE_NORMAL);
	}

	SET_BIT(CANx->CFG, CAN_CFG_SLEEPMODE);
}


/**
  * @brief  Wake up from sleep mode.
  * @param  CANx   CAN instance.
  * @retval None.
  */
void DDL_CAN_WakeUp(CAN_Type *CANx)
{
	CLEAR_BIT(CANx->CFG, CAN_CFG_SLEEPMODE);
}


/**
  * @brief  start sending normal message to the bus.
  * @param  CANx     CAN instance.
  * @param  pHeader  pointer to a CAN_TXHeader_T structure.
  * @param  pData    array containing the payload of the TX frame.
  * @retval DDL status
  */
DDL_Status_T DDL_CAN_SendNormalMessage(CAN_Type *CANx, CAN_TXHeader_T *pHeader, uint8_t *pData)
{
	DDL_Status_T ret = DDL_OK;

	ASSERT_PARAM(IS_CAN_IDTYPE(pHeader->idType));
	ASSERT_PARAM(IS_CAN_RTR(pHeader->rtrType));
	ASSERT_PARAM(IS_CAN_DLC(pHeader->dataLenth));
	if (pHeader->idType == CAN_IDTYPE_STD)
	{
		ASSERT_PARAM(IS_CAN_STDID(pHeader->stdId));
	}
	else
	{
		ASSERT_PARAM(IS_CAN_EXTID(pHeader->extId));
	}

	/* Configure TX ID */
	if (pHeader->idType == CAN_IDTYPE_STD)
	{
		SET_BITMASK(CANx->TXID, CAN_TXID_SFF_Mask, (pHeader->stdId) << CAN_TXID_SFF_Pos);
		CLEAR_BIT(CANx->TXFRMINFO, CAN_TXFRMINFO_EFF);
	}
	else
	{
		WRITE_REG(CANx->TXID, pHeader->extId);
		SET_BIT(CANx->TXFRMINFO, CAN_TXFRMINFO_EFF);
	}

	CLEAR_BIT(CANx->TXFRMINFO, CAN_TXFRMINFO_RTR);
	SET_BITMASK(CANx->TXFRMINFO, CAN_TXFRMINFO_DLC_Mask, pHeader->dataLenth);
	if (GET_BITS(CANx->ST, CAN_ST_TXBUFLOCK) == 0)
	{
		WRITE_REG(CANx->TXDATA0, pData[0] | (pData[1] << 8) | (pData[2] << 16) \
			| (pData[3] << 24));
		WRITE_REG(CANx->TXDATA1, pData[4] | (pData[5] << 8) | (pData[6] << 16) \
			| (pData[7] << 24));
	}
	else
	{
		return DDL_BUSY;
	}

	/* Request transmission */
	SET_BIT(CANx->CTRL, CAN_CTRL_TXREQ);

	return ret;
}


/**
  * @brief  start sending remote reuqest message to the bus.
  * @param  CANx     CAN instance.
  * @param  pHeader  pointer to a CAN_TXHeader_T structure.
  * @retval DDL status
  */
DDL_Status_T DDL_CAN_SendRemoteRequestMessage(CAN_Type *CANx, CAN_TXHeader_T *pHeader)
{
	DDL_Status_T ret = DDL_OK;

	ASSERT_PARAM(IS_CAN_IDTYPE(pHeader->idType));
	ASSERT_PARAM(IS_CAN_RTR(pHeader->rtrType));
	ASSERT_PARAM(IS_CAN_DLC(pHeader->dataLenth));
	if (pHeader->idType == CAN_IDTYPE_STD)
	{
		ASSERT_PARAM(IS_CAN_STDID(pHeader->stdId));
	}
	else
	{
		ASSERT_PARAM(IS_CAN_EXTID(pHeader->extId));
	}

	if (CANx->ST & CAN_ST_TXBUSY)
	{
		return DDL_BUSY;
	}

	/* Configure TX ID */
	if (pHeader->idType == CAN_IDTYPE_STD)
	{
		SET_BITMASK(CANx->TXID, CAN_TXID_SFF_Mask, (pHeader->stdId) << CAN_TXID_SFF_Pos);
		CLEAR_BIT(CANx->TXFRMINFO, CAN_TXFRMINFO_EFF);
	}
	else
	{
		WRITE_REG(CANx->TXID, pHeader->extId);
		SET_BIT(CANx->TXFRMINFO, CAN_TXFRMINFO_EFF);
	}

	SET_BIT(CANx->TXFRMINFO, CAN_TXFRMINFO_RTR);
	SET_BITMASK(CANx->TXFRMINFO, CAN_TXFRMINFO_DLC_Mask, 0);

	/* Request transmission */
	SET_BIT(CANx->CTRL, CAN_CTRL_TXREQ);

	return ret;
}


/**
  * @brief  Abort transmission requests
  * @param  CANx   CAN instance.
  * @retval None
  */
void DDL_CAN_AbortTXRequest(CAN_Type *CANx)
{
	SET_BIT(CANx->CTRL, CAN_CTRL_ABORTTX);
}


/**
  * @brief  Get an CAN frame from the RX FIFO into the message RAM.
  * @param  CANx   CAN instance.
  * @param  pHeader  pointer to a CAN_RXHeader_T structure where the header
  *                  of the RX frame will be stored.
  * @param  pData    pointer to the buffer where the payload of the RX frame will be stored.
  * @retval DDL status
  */
DDL_Status_T DDL_CAN_ReadMessage(CAN_Type *CANx, CAN_RXHeader_T *pHeader, uint8_t *pData)
{
	uint8_t i = 0;
	DDL_Status_T ret = DDL_OK;

	/* Get the header */
	pHeader->idType = (CANx->RXFRMINFO & CAN_RXFRMINFO_FF_Mask) >> CAN_RXFRMINFO_FF_Pos;
	if (pHeader->idType == CAN_IDTYPE_STD)
	{
		pHeader->stdId = (CANx->RXID & CAN_RXID_SFF_Mask) >> CAN_RXID_SFF_Pos;
	}
	else
	{
		pHeader->extId = CANx->RXID;
	}

	pHeader->rtrType = (CANx->RXFRMINFO & CAN_RXFRMINFO_RTR_Mask) >> CAN_RXFRMINFO_RTR_Pos;
	pHeader->dataLenth = CANx->RXFRMINFO & CAN_RXFRMINFO_DLC_Mask;

	/* Get the pData */
	for (i = 0; i < pHeader->dataLenth; i++)
	{
		if (i >= 4)
		{
			pData[i] = (CANx->RXDATA1 >> ((i - 4) * 8)) & 0xFF;
		}
		else
		{
			pData[i] = (CANx->RXDATA0 >> (i * 8)) & 0xFF;
		}
	}

	/* Release the FIFO */
	SET_BIT(CANx->CTRL, CAN_CTRL_RLSERXBUF);

	return ret;
}


/**
  * @brief  Poll for CAN status
  * @param  CANx       CAN instance.
  * @param  stMsk      the CAN status, refer to CAN INTST register.
  * @param  timeout    timeout value in millisecond.
  * @retval DDL status.
  */
DDL_Status_T DDL_CAN_PollForStatus(CAN_Type *CANx, uint32_t stMsk, uint32_t timeout)
{
	uint32_t tickStart = 0U;

	tickStart = DDL_GetTick();
	while (!(CANx->INTST & stMsk))
	{
		if ((DDL_GetTick() - tickStart ) > timeout)
		{
			return DDL_TIMEOUT;
		}
	}

	WRITE_REG(CANx->INTST, CANx->INTST);

	return DDL_OK;
}


#endif /* DDL_CAN_MODULE_ENABLED */

