/**
  ******************************************************************************
  * @file    kpm32xx_ddl_spi.c
  * @author  Kiwi Software Team
  * @brief   SPI DDL module driver.
  *          This file provides firmware functions to manage the following
  *          functionalities of the SPI peripheral:
  *           + peripheral initializes and deInitializes
  *           + full duplex and single line mode(TX/RX)
  *           + polling, interrupt, DMA
  * @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_SPI_MODULE_ENABLED


static DataWidth_T Get_SPIModule_DataWidth(SPI_Type *SPIx)
{
	uint32_t dataWidth = 0;

	dataWidth = SPIx->FRW & 0x1F;

	/* frame width: 1 ~ 32 */
	if (dataWidth <= 7)
	{
		return SPI_DTWIDTH_08BIT;
	}
	else if (dataWidth <= 15)
	{
		return SPI_DTWIDTH_16BIT;
	}
	else
	{
		return SPI_DTWIDTH_32BIT;
	}
}


/**
  * @brief  Make specified SPI active.
  * @param  SPIx        SPI instance.
  * @retval None.
  */
void DDL_SPI_Instance_Active(SPI_Type *SPIx)
{
	__DDL_RCC_SPI_ACTIVE();
	__DDL_RCC_SPI_CLK_RELEASE();
}


/**
  * @brief  Make specified SPI deactive.
  * @param  SPIx        SPI instance.
  * @retval None.
  */
void DDL_SPI_Instance_Deactive(SPI_Type *SPIx)
{
	__DDL_RCC_SPI_DEACTIVE();
	__DDL_RCC_SPI_CLK_HOLD();
}


/**
  * @brief  Configure the elements of structure SPI_Init_T to default values.
  * @param  pSPIStruct Pointer to a SPI_Init_T structure that contains
  *         the configuration information for the given SPI module.
  * @retval None
  */
void DDL_SPI_StructInit(SPI_Init_T *pSPIStruct)
{
    pSPIStruct->baudRate        = 4000000;
    pSPIStruct->clkPolarity     = SPI_CLKPOLARITY_LOW;
    pSPIStruct->clkPhase        = SPI_CLKPHASE_LOW;
    pSPIStruct->mlsb            = SPI_MSB_FIRSTBIT;
	pSPIStruct->transMode       = SPI_FULLDUPLEX_TRANSMODE;
	pSPIStruct->ssTimCfg.ssDest = 0; /* in unit of SCK */
	pSPIStruct->ssTimCfg.frmDly = 0; /* in unit of SCK */
	pSPIStruct->ssTimCfg.hldTim = 8; /* in unit of SCK */
	pSPIStruct->ssTimCfg.setTim = 8; /* in unit of SCK */
	/* support 1 ~ 32bit width */
	pSPIStruct->frameWidth      = SPI_DTWIDTH_08BIT;

	/* CS Pin Controlled by SPI module */
	pSPIStruct->nss[0].nssEn  = 1;
	pSPIStruct->nss[0].hwCtrl = SPI_NSS_HARD;
	pSPIStruct->nss[1].nssEn  = 0;
	pSPIStruct->nss[1].hwCtrl = SPI_NSS_HARD;
	pSPIStruct->workMode = SPI_MODE_MASTER;
}


/**
  * @brief  Initializes the SPI.
  * @param  SPIx   SPI instance.
  * @param  pSPIStruct Pointer to a SPI_Init_T structure that contains
  *         the configuration information for the given SPI module.
  * @retval DDL status
  */
DDL_Status_T DDL_SPI_Init(SPI_Type *SPIx, SPI_Init_T *pSPIStruct)
{
	uint32_t config0 = 0;
	uint32_t clkFrq = 0;
	uint32_t baudConfig = 0;
	uint32_t i = 0;
	uint32_t config1 = 0;

	config0 |= pSPIStruct->workMode | pSPIStruct->mlsb | pSPIStruct->clkPolarity | \
	           pSPIStruct->clkPhase | pSPIStruct->transMode;
	WRITE_REG(SPIx->CFG, config0);

	config1 = pSPIStruct->ssTimCfg.ssDest << SPI_CSTIM_SSDST_Pos | pSPIStruct->ssTimCfg.frmDly << SPI_CSTIM_SDLY_Pos | \
	      pSPIStruct->ssTimCfg.hldTim << SPI_CSTIM_HLDT_Pos  | pSPIStruct->ssTimCfg.setTim << SPI_CSTIM_STPT_Pos;
	WRITE_REG(SPIx->CSTIM, config1);

	/* Only Configure data frame size*/
	WRITE_REG(SPIx->FRW, pSPIStruct->frameWidth);

	/* Make all data both in TX and RX FIFO empty, due to some unexpected data remained in FIFO*/
	__DDL_SPI_CLEAR_TXFIFO(SPIx);
	__DDL_SPI_CLEAR_RXFIFO(SPIx);

	for (i = 0; i < __MAX_CS_CNT__; i++)
	{
		if (pSPIStruct->nss[i].nssEn)
		{
			/* CS pin controlled by Hardware, only used in Master Mode */
			if (pSPIStruct->nss[i].hwCtrl == SPI_NSS_HARD)
			{
				SET_BITMASK(SPIx->CSCTL, 0x03, 0x01UL << (i + SPI_CSCTL_SS0HWEN_Pos));
			}
			else
			{
				CLEAR_BIT(SPIx->CSCTL, 0x03);
				/* Output High Level as default */
				SET_BIT(SPIx->CSCTL, 0x01UL << (i + SPI_CSCTL_SS0OUT_Pos));
				/* Enable Software Control */
				SET_BIT(SPIx->CSCTL, 0x01UL << (i + SPI_CSCTL_SS0SFTEN_Pos));
			}
		}
	}

	/* FCLK as SPI clock source */
	clkFrq = DDL_RCC_GetSysClockFreq() >> 1;
	baudConfig = clkFrq / pSPIStruct->baudRate;
	__DDL_SPI_BAUDRATE_CONFIG(SPIx, baudConfig - 1);

	/* Finally, enable SPI Module */
	__DDL_SPI_MODULE_ENABLE(SPIx);

	return DDL_OK;
}


/**
  * @brief  SPI as master or slave transmits an amount of data through polling mode.
  * @param  SPIx     SPI instance.
  * @param  pData    pointer to data buffer.
  * @param  size     the amount of data to be sent.
  * @param  timeout  timeout duration.
  * @retval DDL status
  */
DDL_Status_T DDL_SPI_Transmit(SPI_Type *SPIx, uint8_t *pData, uint16_t size, uint32_t timeout)
{
	uint32_t tickstart;
	DDL_Status_T errorcode = DDL_OK;
	DataWidth_T dataWidth;

	/* Check data-width */
	dataWidth = Get_SPIModule_DataWidth(SPIx);

	/* Empty all TX FIFO */
	__DDL_SPI_CLEAR_TXFIFO(SPIx);
	__DDL_SPI_CLEAR_RXFIFO(SPIx);

	/* It makes no side effects, when CS is configured to Software Control */
	__DDL_SPI_MULTIDATATRANS_CONFIG(SPIx, (size - 1));
	WRITE_REG(SPI->INTST, 0xFFFFFFF);
	
	tickstart = DDL_GetTick();
	/* Caution: when in slave mode, data pushed into TX FIFO will be hold till the master requests a data-transmission */
	while (size > 0U)
	{
		/* To send data, just wait until TX FIFO empty flag is set*/
		if (dataWidth == SPI_DTWIDTH_08BIT)
		{
			SPIx->DR = *((uint8_t *)pData);
			pData += sizeof(uint8_t);
		}
		if (dataWidth == SPI_DTWIDTH_16BIT)
		{
			SPIx->DR = *((uint16_t *)pData);
			pData += sizeof(uint16_t);
		}
		if (dataWidth == SPI_DTWIDTH_32BIT)
		{
			SPIx->DR = *((uint32_t *)pData);
			pData += sizeof(uint32_t);
		}
		size--;

		/* Hardware auto-clear */
		while(!(SPIx->ST & SPI_ST_TXFFEMPT))
		{
			/* timeout management. Time consumed should contain both data-transmitting time and data-transmission-waiting time */
			if ((DDL_GetTick() - tickstart) >=  timeout)
			{
				errorcode = DDL_TIMEOUT;
				goto error;
			}
		}
	}

	while(!(SPIx->INTST & SPI_INTST_SNDCMPLT))
	{
		/* timeout management. Time consumed should contain both data-transmitting time and data-transmission-waiting time */
		if ((DDL_GetTick() - tickstart) >=  timeout)
		{
			errorcode = DDL_TIMEOUT;
			goto error;
		}
	}
	WRITE_REG(SPIx->INTST, SPI_INTST_SNDCMPLT) ;

	/* Clear overrun flag in 2 Lines communication mode because received is not read out and still resided in RX FIFO*/

	/* Full duplex mode will receive data from salve while transmitting data.
	 * And it's quite possible that the received data count will exceed the RX FIFO which lead to
	 * RX FIFO overrun flag-raising.
	 *
	 * However, the routine body only implements TX-Only Mode. So, SPI hardware module never receives data and
	 * puts them into RX FIFO.
	 * Anyway, never mind, making RX FIFO empty has no side-effects here.
	 */
	__DDL_SPI_CLEAR_RXFIFO(SPIx);

error:

	return errorcode;
}


/**
  * @brief  SPI as master to receives an amount of data through polling mode.
  * @param  SPIx     SPI instance.
  * @param  pData    pointer to data buffer.
  * @param  size     the amount of data to be sent.
  * @param  timeout  timeout duration.
  * @retval DDL status
  */
DDL_Status_T DDL_SPI_MasterReceive(SPI_Type *SPIx, uint8_t *pData, uint16_t size, uint32_t timeout)
{
	uint32_t tickstart;
	DDL_Status_T errorcode = DDL_OK;
	DataWidth_T dataWidth;

	tickstart = DDL_GetTick();

	/* Check data-width */
	dataWidth = Get_SPIModule_DataWidth(SPIx);

	/* Empty all TX And RX FIFO */
	__DDL_SPI_CLEAR_TXFIFO(SPIx);
	__DDL_SPI_CLEAR_RXFIFO(SPIx);

	/* It makes no side effects, when CS is configured as Software Control */
	__DDL_SPI_MULTIDATATRANS_CONFIG(SPIx, (size - 1));
	WRITE_REG(SPI->INTST, 0xFFFFFFF);
	
	while (size > 0U)
	{
		if (dataWidth == SPI_DTWIDTH_08BIT)
		{
			SPIx->DR = (uint8_t)__SPI_08BIT_DUMMY_DATA__;
		}
		if (dataWidth == SPI_DTWIDTH_16BIT)
		{
			SPIx->DR = (uint16_t)__SPI_16BIT_DUMMY_DATA__;
		}
		if (dataWidth == SPI_DTWIDTH_32BIT)
		{
			SPIx->DR = (uint32_t)__SPI_32BIT_DUMMY_DATA__;
		}

		/* To receive data, master should send data first.
		 * To send data, just wait until TX FIFO empty flag is set
		 */
		/* Hardware auto-clear */
        while(!(SPIx->ST & SPI_ST_TXFFEMPT)) ;

		/* Wait till RX FIFO is not empty. Data will be shifted into RX FIFO while data is transmitting */
		while (!(SPIx->ST & SPI_ST_RXFFNEMPT))
		{
			/* timeout management. Time consumed should contain both data-transmitting time and data-transmission-waiting time */
			if ((DDL_GetTick() - tickstart) >=  timeout)
			{
				return DDL_TIMEOUT;
			}
		}

		if (dataWidth == SPI_DTWIDTH_08BIT)
		{
			*((uint8_t *)pData) = (uint8_t)(SPIx->DR);
			pData += sizeof(uint8_t);
		}
		if (dataWidth == SPI_DTWIDTH_16BIT)
		{
			*((uint16_t *)pData) = (uint16_t)(SPIx->DR);
			pData += sizeof(uint16_t);
		}
		if (dataWidth == SPI_DTWIDTH_32BIT)
		{
			*((uint32_t *)pData) = (uint32_t)(SPIx->DR);
			pData += sizeof(uint32_t);
		}

		size--;
	}

	WRITE_REG(SPIx->INTST, SPI_INTST_SNDCMPLT) ;

	return errorcode;
}


/**
  * @brief  SPI as slave to receives an amount of data through polling mode.
  * @param  SPIx     SPI instance.
  * @param  pData    pointer to data buffer.
  * @param  size     the amount of data to be sent.
  * @param  timeout  timeout duration.
  * @retval DDL status
  */
DDL_Status_T DDL_SPI_SlaveReceive(SPI_Type *SPIx, uint8_t *pData, uint16_t size, uint32_t timeout)
{
	uint32_t tickstart;
	DDL_Status_T errorcode = DDL_OK;
	DataWidth_T dataWidth;

	/* Initialize tickstart for timeout management*/
	tickstart = DDL_GetTick();

	/* Check data-width */
	dataWidth = Get_SPIModule_DataWidth(SPIx);

	/* Empty all TX And RX FIFO */
	__DDL_SPI_CLEAR_TXFIFO(SPIx);
	__DDL_SPI_CLEAR_RXFIFO(SPIx);

	/* It makes no side effects, when CS is configured as Software Control */
	__DDL_SPI_MULTIDATATRANS_CONFIG(SPIx, (size - 1));
	WRITE_REG(SPI->INTST, 0xFFFFFFF);
	
	while (size > 0U)
	{
		while(!(SPIx->ST & SPI_ST_RXFFNEMPT))
		{
			if ((DDL_GetTick() - tickstart) >=  timeout)
			{
				return DDL_TIMEOUT;
			}
		}

		if (dataWidth == SPI_DTWIDTH_08BIT)
		{
			*((uint8_t *)pData) = (uint8_t)(SPIx->DR);
			pData += sizeof(uint8_t);
		}
		if (dataWidth == SPI_DTWIDTH_16BIT)
		{
			*((uint16_t *)pData) = (uint16_t)(SPIx->DR);
			pData += sizeof(uint16_t);
		}
		if (dataWidth == SPI_DTWIDTH_32BIT)
		{
			*((uint32_t *)pData) = (uint32_t)(SPIx->DR);
			pData += sizeof(uint32_t);
		}

		size--;
	}
	WRITE_REG(SPIx->INTST, SPI_INTST_SNDCMPLT);

	return errorcode;
}


/**
  * @brief  SPI as master or slave (Full-Duplex) transmits and receives
  *         an amount of data through polling mode.
  * @param  SPIx   SPI instance.
  * @param  pData  pointer to data buffer.
  * @param  size   the amount of data to be sent and receive.
  * @param  timeout  timeout duration.
  * @retval DDL status
  */
DDL_Status_T DDL_SPI_TransmitReceive(SPI_Type *SPIx, uint8_t *pTxData, uint8_t *pRxData, uint16_t size, uint32_t timeout)
{
	uint32_t      tickstart;
	uint8_t      txallowed = 1U;
	DDL_Status_T  errorcode = DDL_OK;
	DataWidth_T   dataWidth;

	/* Check data-width */
	dataWidth = Get_SPIModule_DataWidth(SPIx);

	/* Empty all TX/RX FIFO */
	__DDL_SPI_CLEAR_TXFIFO(SPIx);
	__DDL_SPI_CLEAR_RXFIFO(SPIx);

	/* It makes no side effects, when CS is configured to Software Control */
	__DDL_SPI_MULTIDATATRANS_CONFIG(SPIx, (size - 1));

	tickstart = DDL_GetTick();

	while (size > 0U)
	{
		if ((SPIx->ST & SPI_ST_TXFFEMPT) && (txallowed == 1U))
		{
			if (dataWidth == SPI_DTWIDTH_08BIT)
			{
				SPIx->DR = *((uint8_t *)pTxData);
				pTxData += sizeof(uint8_t);
			}
			if (dataWidth == SPI_DTWIDTH_16BIT)
			{
				SPIx->DR = *((uint16_t *)pTxData);
				pTxData += sizeof(uint16_t);
			}
			if (dataWidth == SPI_DTWIDTH_32BIT)
			{
				SPIx->DR = *((uint32_t *)pTxData);
				pTxData += sizeof(uint32_t);
			}

			txallowed = 0U;
		}

		/* Read out the data, when there's one frame of data in RX FIFO */
		if ((SPIx->ST & SPI_ST_RXFFNEMPT) && (txallowed == 0U))
		{
			if (dataWidth == SPI_DTWIDTH_08BIT)
			{
				*((uint8_t *)pRxData) = (uint8_t)(SPIx->DR);
				pRxData += sizeof(uint8_t);
			}
			if (dataWidth == SPI_DTWIDTH_16BIT)
			{
				*((uint16_t *)pRxData) = (uint16_t)(SPIx->DR);
				pRxData += sizeof(uint16_t);
			}
			if (dataWidth == SPI_DTWIDTH_32BIT)
			{
				*((uint32_t *)pRxData) = (uint32_t)(SPIx->DR);
				pRxData += sizeof(uint32_t);
			}

			size--;
			txallowed = 1U;
		}

		if ((DDL_GetTick() - tickstart) >= timeout)
		{
			errorcode = DDL_TIMEOUT;
			break;
		}
	}

	WRITE_REG(SPIx->INTST, SPI_INTST_SNDCMPLT);

	return errorcode;
}


/**
  * @brief  Enable SPI interrupt with specified type.
  * @param  SPIx     SPI instance.
  * @param  intrMask SPI interrupt type.
  * @retval None
  */
void DDL_SPI_IntEnable(SPI_Type *SPIx, uint32_t intrMask)
{
    SET_BIT(SPIx->INTR, intrMask);

	/* Enable SPI global interrupt */
	__DDL_SPI_GLOBALIT_ENABLE(SPIx);
}


/**
  * @brief  Disable SPI interrupt with specified type.
  * @param  SPIx     SPI instance.
  * @param  intrMask SPI interrupt type.
  * @retval None
  */
void DDL_SPI_IntDisable(SPI_Type *SPIx, uint32_t intrMask)
{
    CLEAR_BIT(SPIx->INTR, intrMask);

	/* Disable SPI global interrupt */
	__DDL_SPI_GLOBALIT_DISABLE(SPIx);
}


/**
  * @brief  Enable SPI TX DMA with specified trigger level.
  * @param  SPIx      SPI instance.
  * @param  trigLevel transmit DMA trigger level.
  * @retval None
  */
void DDL_SPI_TXDMAEnable(SPI_Type *SPIx, uint8_t trigLevel)
{
	/* Set Tx DMA threshold for FIFO */
	__DDL_SPI_TXDMALVL_CONFIG(SPIx, trigLevel);

	__DDL_SPI_TXDMA_ENABLE(SPIx);
}


/**
  * @brief  Enable SPI RX DMA with specified trigger level.
  * @param  SPIx      SPI instance.
  * @param  trigLevel receive DMA trigger level.
  * @retval None
  */
void DDL_SPI_RXDMAEnable(SPI_Type *SPIx, uint8_t trigLevel)
{
	/* Set RX DMA threshold for FIFO */
	__DDL_SPI_RXDMALVL_CONFIG(SPIx, trigLevel);

	__DDL_SPI_RXDMA_ENABLE(SPIx);
}

#endif /* DDL_SPI_MODULE_ENABLED */
