/**
  ******************************************************************************
  * @file    kpm32xx_ddl_adc.c
  * @author  Kiwi Software Team
  * @brief   ADC DDL module driver.
  *          This file provides firmware functions to manage the following
  *          functionalities of the ADC peripheral:
  *           + peripheral initializes and deInitializes
  *           + different mode of ADC operation
  *           + 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 <string.h>
#include "kpm32xx_ddl.h"



#ifdef DDL_ADC_MODULE_ENABLED

#define ADC_PPB_BASE_ADDR             (0x00000110UL)
typedef struct
{
	__IO uint32_t CFG;                 /* ADC PPB Configure Register,       address offset: 0x00      */
	__IO uint32_t CALOFF;              /* ADC PPB Calibration Offset Register, address offset: 0x04   */
	__IO uint32_t REFOFF;              /* ADC PPB Reference Offset Register, address offset: 0x08     */
	__IO uint32_t COMPH;               /* ADC PPB Compare High Register, address offset: 0x0C         */
	__IO uint32_t COMPL;               /* ADC PPB Compare Low Register, address offset: 0x10          */
	__IO uint32_t RESERVED0[3];        /* Reserved,                     address offset : 0x14 ~ 0x1C  */
} ADC_PPB_T;


/**
  * @brief  Make specified ADC active.
  * @param  ADCx        ADC instance.
  * @retval None.
  */
void DDL_ADC_Instance_Active(ADC_Type *ADCx)
{
	if (ADCx == ADC0)
	{
		__DDL_RCC_ADC0_ACTIVE();
		__DDL_RCC_ADC0_CLK_RELEASE();
	}
	else
	{
		__DDL_RCC_ADC1_ACTIVE();
		__DDL_RCC_ADC1_CLK_RELEASE();
	}
}


/**
  * @brief  Make specified ADC deactive.
  * @param  ADCx        ADC instance.
  * @retval None.
  */
void DDL_ADC_Instance_Deactive(ADC_Type *ADCx)
{
	if (ADCx == ADC0)
	{
		__DDL_RCC_ADC0_DEACTIVE();
		__DDL_RCC_ADC0_CLK_HOLD();
	}
	else
	{
		__DDL_RCC_ADC1_DEACTIVE();
		__DDL_RCC_ADC1_CLK_HOLD();
	}
}


/**
  * @brief  Configure the elements of structure ADC_Init_T to default values.
  * @param  pADCStruct Pointer to a ADC_Init_T structure that contains
  *         the configuration information for the given ADC module.
  * @retval None
  */
void DDL_ADC_StructInit(ADC_Init_T *pADCStruct)
{
	pADCStruct->zeroOffsetCal   = 0;
	pADCStruct->resolution      = RESOLUTION_12BIT;
	pADCStruct->socPriority     = ADC_PRI_ALL_ROUND_ROBIN;
	pADCStruct->adcClkPrescaler = 9;
}


/**
  * @brief  Initialize the ADC peripheral according to the specified parameters
  *         in the ADC_Init_T.
  * @param  ADCx        ADC instance.
  * @param  pADCStruct  pointer to a ADC_Init_T structure that contains
  *         the configuration information for the specified ADC.
  * @retval None.
  */
void DDL_ADC_Init(ADC_Type *ADCx, ADC_Init_T *pADCStruct)
{
	RCC_ADCInit_T clockConfig;

	/* Configure ADC clock */
	clockConfig.adcPrescalser = pADCStruct->adcClkPrescaler;
	clockConfig.periphClkSrcSelection = (ADCx == ADC0) ? (RCC_PERIPHCLK_ADC0) : RCC_PERIPHCLK_ADC1;
	DDL_RCC_ADC_PeriphCLKConfig(&clockConfig);

	/* Configure zero offset calibration */
	WRITE_REG(ADCx->OFFSETCAL, pADCStruct->zeroOffsetCal & ADC_OFFSETCAL_VALUE_Msk);
	/* Configure ADC high priority */
	SET_BITMASK(ADCx->PRIO, ADC_PRIO_HIGHSEL_Msk, pADCStruct->socPriority);
	/* Configure resolution */
	SET_BITMASK(ADCx->CTRL, ADC_CTRL_RESOLUTION_Msk, pADCStruct->resolution << ADC_CTRL_RESOLUTION_Pos);
	/* Set ADC work mode */
	SET_BITMASK(ADCx->CTRL, ADC_CTRL_MODE_Msk, ADC_CTRL_MODE_SINGLE);

	__DDL_ADC_ENABLE(ADCx);
	/* There should be more than 5us between ADC enable and ADC trigger. */
	DDL_Delay(1);
}


/**
  * @brief  Return the ADC PPB base address depending on PPB number
  * @param  ADCx   ADC instance.
  * @param  PPBx   PPB index.
  * @retval PPB base address
  */
static uint32_t ADC_CalcPPBBaseAddr(ADC_Type *ADCx, ADC_PPBNumber_T PPBx)
{
	uint32_t ppbAddr = 0;

	ppbAddr = (uint32_t)((uint32_t)(ADCx) + ADC_PPB_BASE_ADDR + PPBx * sizeof(ADC_PPB_T));

	return ppbAddr;
}


/**
  * @brief  Configures the SOC to use.
  * @param  ADCx     ADC instance.
  * @param  SOCx     ADC SOC index.
  * @param  pConfig  pointer to a ADC_SOCConfig_T that contains
  *                  the SOC information.
  * @retval None
  */
void DDL_ADC_ConfigSOC(ADC_Type *ADCx, ADC_SOCNumber_T SOCx, ADC_SOCConfig_T *pConfig)
{
	SET_BITMASK(ADCx->SOCCONF[SOCx], ADC_SOCCONF_CHNSEL_Msk, pConfig->channel << ADC_SOCCONF_CHNSEL_Pos);
	SET_BITMASK(ADCx->SOCCONF[SOCx], ADC_SOCCONF_SAMPLETIME_Msk, pConfig->sampleTime << ADC_SOCCONF_SAMPLETIME_Pos);
	SET_BIT(ADCx->ENABLE, 0x1U << pConfig->channel);

	if (pConfig->hwTrigger)
	{
		SET_BITMASK(ADCx->SOCCONF[SOCx], ADC_SOCCONF_HWTRIGSEL_Msk, pConfig->ebusChannel << ADC_SOCCONF_HWTRIGSEL_Pos);
	}
}


/**
  * @brief  Configures the post processing block.
  * @param  ADCx        ADC instance.
  * @param  PPBx        PPB index.
  * @param  pConfig     pointer to an ADC_PPBConfig_T structure
  *                     that contains the configuration information of ADC PPB.
  * @retval DDL status
  */
void DDL_ADC_ConfigPPB(ADC_Type *ADCx, ADC_PPBNumber_T PPBx, ADC_PPBConfig_T *pConfig)
{
	ADC_PPB_T *ppbAddr = NULL;

	ppbAddr = (ADC_PPB_T *)ADC_CalcPPBBaseAddr(ADCx, PPBx);

	/* Set PPB parameters */
	SET_BITMASK(ppbAddr->CFG, ADC_PPBCONF_CALSEL_Msk, pConfig->enableTwosComplement << ADC_PPBCONF_CALSEL_Pos);
	SET_BITMASK(ppbAddr->CFG, ADC_PPBCONF_SOCSEL_Msk, pConfig->soc << ADC_PPBCONF_SOCSEL_Pos);
	SET_BITMASK(ppbAddr->CALOFF, ADC_PPBOFFCAL_VALUE_Msk, (uint16_t)pConfig->calOffset & ADC_PPBOFFCAL_VALUE_Msk);

	WRITE_REG(ppbAddr->REFOFF, pConfig->refOffset & ADC_PPBOFFREF_VALUE_Msk);
	WRITE_REG(ppbAddr->COMPH, (uint16_t)pConfig->highThreshold & ADC_PPBCOMPH_VALUE_Msk);
	WRITE_REG(ppbAddr->COMPL, (uint16_t)pConfig->lowThreshold & ADC_PPBCOMPL_VALUE_Msk);

	SET_BIT(ppbAddr->CFG, 0x1);
}


/**
  * @brief  Enable post processing block (PPB).
  * @param  ADCx        ADC instance.
  * @param  PPBx        PPB index.
  *
  * @retval None.
  */
void DDL_ADC_EnablePPBMode(ADC_Type *ADCx, ADC_PPBNumber_T PPBx)
{
	ADC_PPB_T *ppbAddr = NULL;

	ppbAddr = (ADC_PPB_T *)ADC_CalcPPBBaseAddr(ADCx, PPBx);
	SET_BIT(ppbAddr->CFG, 0x1);
}


/**
  * @brief  Disable post processing block (PPB).
  * @param  ADCx        ADC instance.
  * @param  PPBx        PPB index.
  *
  * @retval None.
  */
void DDL_ADC_DisablePPBMode(ADC_Type *ADCx, ADC_PPBNumber_T PPBx)
{
	ADC_PPB_T *ppbAddr = NULL;

	ppbAddr = (ADC_PPB_T *)ADC_CalcPPBBaseAddr(ADCx, PPBx);
	CLEAR_BIT(ppbAddr->CFG, 0x1);
}


/**
  * @brief  Configure Burst to use.
  * @param  ADCx     ADC instance.
  * @param  pConfig  pointer to a ADC_BurstConfig_T that contains
  *                  the SOC information.
  * @retval None.
  */
void DDL_ADC_BurstConfig(ADC_Type *ADCx, ADC_BurstConfig_T *pConfig)
{
	SET_BITMASK(ADCx->CTRL, ADC_CTRL_BURSTLEN_Msk, pConfig->burstLen << ADC_CTRL_BURSTLEN_Pos);
	SET_BIT(ADCx->CTRL, ADC_CTRL_BURSTEN);

	if (pConfig->hwTrigger)
	{
		SET_BITMASK(ADCx->CTRL, ADC_CTRL_BURSTHWTRIGSEL_Msk, pConfig->triggerSrc << ADC_CTRL_BURSTHWTRIGSEL_Pos);
	}
}


/**
  * @brief  Poll for conversion event
  * @param  ADCx       ADC instance.
  * @param  eventType  the ADC event type, ref to ADC_Event_T
  * @param  timeout    timeout value in millisecond.
  * @retval DDL status.
  */
DDL_Status_T DDL_ADC_PollForEvent(ADC_Type *ADCx, uint32_t eventType, uint32_t timeout)
{
	uint32_t tickStart = 0U;

	tickStart = DDL_GetTick();
	while (!(__DDL_ADC_GET_FLAG(ADCx, eventType)))
	{
		if ((DDL_GetTick() - tickStart ) > timeout)
		{
			return DDL_TIMEOUT;
		}
	}

	/* Clear event flag */
	WRITE_REG(ADCx->CLEAR, eventType | ADC_STATUS_SOCANYINT);

	return DDL_OK;
}


/**
  * @brief  Poll for ADC idle
  * @param  ADCx     ADC instance.
  * @param  timeout  timeout value in millisecond.
  * @retval DDL status.
  */
DDL_Status_T DDL_ADC_PollForIdle(ADC_Type *ADCx, uint32_t timeout)
{
	uint32_t tickStart = 0U;

	tickStart = DDL_GetTick();
	while ((__DDL_ADC_GET_FLAG(ADCx, ADC_STATUS_BUSY)))
	{
		if ((DDL_GetTick() - tickStart ) > timeout)
		{
			return DDL_TIMEOUT;
		}
	}

	/* Clear event flag */
	WRITE_REG(ADCx->CLEAR, 0xFFFFFFFF);

	return DDL_OK;
}


/**
  * @brief  Enable ADC interrupt with specified type.
  * @param  ADCx      ADC instance.
  * @param  intrMask  ADC interrupt type.
  * @retval None.
  */
void DDL_ADC_IntEnable(ADC_Type *ADCx, uint32_t intrMask)
{
	__DDL_ADC_ENABLE_IT(ADCx, intrMask);
}


/**
  * @brief  Disable ADC interrupt with specified type.
  * @param  ADCx      ADC instance.
  * @param  intrMask  ADC interrupt type.
  * @retval None.
  */
void DDL_ADC_IntDisable(ADC_Type *ADCx, uint32_t intrMask)
{
	__DDL_ADC_DISABLE_IT(ADCx, intrMask);
}


/**
  * @brief  Enable ADC DMA function.
  * @param  ADCx    ADC instance.
  * @retval None
  */
void DDL_ADC_DmaEnable(ADC_Type *ADCx)
{
	SET_BIT(ADCx->CTRL, ADC_CTRL_DMA);
}


/**
  * @brief  Disable ADC DMA function.
  * @param  ADCx    ADC instance.
  * @retval None
  */
void DDL_ADC_DmaDisable(ADC_Type *ADCx)
{
	CLEAR_BIT(ADCx->CTRL, ADC_CTRL_DMA);
}


/**
  * @brief  Gets the converted value from the specify SOC.
  * @param  ADCx   ADC instance.
  * @param  SOCx   Specify which SOC to read, from 0~15.
  * @retval converted value
  */
uint16_t DDL_ADC_ReadSOCResult(ADC_Type *ADCx, ADC_SOCNumber_T SOCx)
{
	return (uint16_t)(ADCx->RESULT[SOCx]);
}


/**
  * @brief  Gets the converted value from the specify PPB.
  * @param  ADCx    ADC instance.
  * @param  PPBx    Specify which PPB to read, from 0~3.
  * @retval converted value
  */
uint16_t DDL_ADC_ReadPPBResult(ADC_Type *ADCx, ADC_PPBNumber_T PPBx)
{
	return (uint16_t)(ADCx->PPBRESULT[PPBx]);
}


/**
  * @brief  Start specify SOC conversion.
  * @param  ADCx  ADC instance.
  * @param  SOCx  the SOC need to trigger.
  * @retval None
  */
void DDL_ADC_StartSOC_BySwTrig(ADC_Type *ADCx, ADC_SOCNumber_T SOCx)
{
	__DDL_ADC_SOFT_TRIGGER_START(ADCx, SOCx);
}


/**
  * @brief  Trigger specified ADC SOC to sample by Hardware sources.
  * @param  ADCx     ADC instance.
  * @param  SOCx     ADC SOC index.
  * @retval None.
  */
void DDL_ADC_StartSOC_ByHwTrig(ADC_Type *ADCx, ADC_SOCNumber_T SOCx)
{
	SET_BIT(ADCx->SOCCONF[SOCx], ADC_SOCCONF_HWTRIGEN);
}


/**
  * @brief  Stop one ADC SOC to sample by Hardware sources.
  * @param  ADCx     ADC instance.
  * @param  SOCx     ADC SOC index.
  * @retval None.
  */
void DDL_ADC_StopSOC_ByHwTrig(ADC_Type *ADCx, ADC_SOCNumber_T SOCx)
{
	CLEAR_BIT(ADCx->SOCCONF[SOCx], ADC_SOCCONF_HWTRIGEN);
}


/**
  * @brief  ADC Burst started by software trigger.
  * @param  ADCx     ADC instance.
  * @retval None.
  */
void DDL_ADC_StartBurst_BySwTrig(ADC_Type *ADCx)
{
	__DDL_ADC_BURST_TRIGGER_START(ADCx);
}


/**
  * @brief  ADC Burst started by Hardware sources.
  * @param  ADCx     ADC instance.
  * @retval None.
  */
void DDL_ADC_StartBurst_ByHwTrig(ADC_Type *ADCx)
{
	SET_BIT(ADCx->CTRL, ADC_CTRL_BURSTHWTRIGEN);
}


/**
  * @brief  Stop ADC Burst by Hardware sources.
  * @param  ADCx     ADC instance.
  * @retval None.
  */
void DDL_ADC_StopBurst_ByHwTrig(ADC_Type *ADCx)
{
	CLEAR_BIT(ADCx->CTRL, ADC_CTRL_BURSTHWTRIGEN);
}

#endif /* DDL_ADC_MODULE_ENABLED */
