/**
  ******************************************************************************
  * @file    kpm32xx_ddl_gpio.c
  * @author  Kiwi Software Team
  * @brief   This file provides firmware functions to manage the following
  *          functionalities:
  *          1. GPIO initialization
  *          2. Set/Reset/Toggle one target GPIO
  *
  * @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_GPIO_MODULE_ENABLED


/* !!!Caution!!!: GPIO will Never generate Interrupts */

#define GPIO_NUMBER           (16U)


/**
  * @brief  get GPIO index of the given GPIO port.
  * @param  GPIOx  pointer to a GPIO_Type structure.
  *
  * @retval GPIO Index
  */
static uint32_t GPIO_GetPortIndex(GPIO_Type *GPIOx)
{
	/* Each GPIO peripheral always occupy memory space of 0x1000 = 2^12 bytes */
	uint32_t index = ((uint32_t)(GPIOx) - (uint32_t)(GPIOX_BASE)) >> 12;

    return index;
}


/**
  * @brief  Make GPIO peripheral active.
  * @param  None.
  *
  * @retval None.
  */
void DDL_GPIO_Instance_Active(void)
{
	/* Do Clock and Reset GPIO peripheral */
	__DDL_RCC_GPIO_ACTIVE();
	__DDL_RCC_GPIO_CLK_RELEASE();
}

static uint32_t GPIO_GetPinIndex(uint32_t gpioPin)
{
	uint32_t position = 0;
	
	for (position = 0; position < GPIO_NUMBER; position++)
	{	
		if ((((uint32_t)0x01) << position) == (gpioPin))
		{
			break;
		}
	}

	return position;
}


/**
  * @brief  Configure GPIO as Input mode.
  * @param  GPIOx  pointer to a GPIO_Type structure.
  * @param  gpioPin GPIO pin.
  * @param  pullMsk GPIO pull up/down or none-pull.
  *
  * @retval None.
  */
void DDL_GPIO_Config2Input(GPIO_Type *GPIOx, uint32_t gpioPin, uint32_t pullMsk)
{
	ASSERT_PARAM(IS_GPIO_ALL_INSTANCE(GPIOx));

	uint32_t index = GPIO_GetPortIndex(GPIOx);
	uint32_t position = GPIO_GetPinIndex(gpioPin);
	uint32_t shiftPin = gpioPin << ((index % 2) * 16);
	uint32_t newIndex = index / 2;

	/* Refer to reference manual to understand why 1 left-rotation here*/
	MODIFY_REG(SYSCFG->IOMDSEL[index], (SYSCONF_IOMDSEL_PIN_MASK << (position << 1)), (SYSCONF_IOMDSEL_PIN_GPIO << (position << 1)));
	GPIOx->OEC = gpioPin;

	if (pullMsk == GPIO_PULLUP)
	{
		SET_BIT(SYSCFG->PDPLUP[newIndex], (shiftPin));
	}

	if (pullMsk == GPIO_PULLDOWN)
	{
		SET_BIT(SYSCFG->PDPLDN[newIndex], (shiftPin));
	}

	if (pullMsk == GPIO_NOPULL)
	{
		CLEAR_BIT(SYSCFG->PDPLUP[newIndex], (shiftPin));
		CLEAR_BIT(SYSCFG->PDPLDN[newIndex], (shiftPin));
	}
}


/**
  * @brief  Configure the drive capacity of one GPIO pin.
  * @param  GPIOx  pointer to a GPIO_Type structure.
  * @param  gpioPin GPIO pin.
  * @param  drvCapacity GPIO IO driving capacity.
  *
  * @retval None.
  */
void DDL_GPIO_DrvCapcityConfig(GPIO_Type *GPIOx, uint32_t gpioPin, uint32_t drvCapacity)
{
	ASSERT_PARAM(IS_GPIO_ALL_INSTANCE(GPIOx));

	uint32_t index = GPIO_GetPortIndex(GPIOx);
	uint32_t position = GPIO_GetPinIndex(gpioPin);
	uint32_t shiftPin = gpioPin << ((index % 2) * 16);
	uint32_t newIndex = index / 2;

	if (drvCapacity)
	{
		SET_BIT(SYSCFG->PDDRV[newIndex], (shiftPin));
	}
	else
	{
		CLEAR_BIT(SYSCFG->PDDRV[newIndex], (shiftPin));
	}
}


/**
  * @brief  Configure open drain feature of one GPIO pin.
  * @param  GPIOx  pointer to a GPIO_Type structure.
  * @param  gpioPin GPIO pin.
  * @param  openDrain GPIO IO open drain.
  *
  * @retval None.
  */
void DDL_GPIO_OpenDrainConfig(GPIO_Type *GPIOx, uint32_t gpioPin, uint32_t openDrain)
{
	ASSERT_PARAM(IS_GPIO_ALL_INSTANCE(GPIOx));

	uint32_t index = GPIO_GetPortIndex(GPIOx);
	uint32_t position = GPIO_GetPinIndex(gpioPin);
	uint32_t shiftPin = gpioPin << ((index % 2) * 16);
	uint32_t newIndex = index / 2;

	if (openDrain)
	{
		SET_BIT(SYSCFG->PDOD[newIndex], (shiftPin));
	}
	else
	{
		CLEAR_BIT(SYSCFG->PDOD[newIndex], (shiftPin));
	}
}


/**
  * @brief  Configure GPIO as Output mode.
  * @param  GPIOx  pointer to a GPIO_Type structure.
  * @param  gpioPin GPIO pin.
  * @param  initValue GPIO initial value before output.
  * @param  pullMsk GPIO pull up/down or none-pull.
  *
  * @retval None.  
  */
void DDL_GPIO_Config2Output(GPIO_Type *GPIOx, uint32_t gpioPin, uint32_t initValue, uint32_t pullMsk)
{
	ASSERT_PARAM(IS_GPIO_ALL_INSTANCE(GPIOx));

	uint32_t index = GPIO_GetPortIndex(GPIOx);
	uint32_t position = GPIO_GetPinIndex(gpioPin);
	uint32_t shiftPin = gpioPin << ((index % 2) * 16);
	uint32_t newIndex = index / 2;

	MODIFY_REG(SYSCFG->IOMDSEL[index], (SYSCONF_IOMDSEL_PIN_MASK << (position << 1)), (SYSCONF_IOMDSEL_PIN_GPIO << (position << 1)));
	/* Configure Initial value before Output enabled */
	if(initValue != GPIO_PIN_RESET)
	{
		GPIOx->DOSR = (uint32_t)(gpioPin) << 16U;
	}
	else
	{
		GPIOx->DOSR = gpioPin;
	}

	GPIOx->OES = gpioPin;

	if (pullMsk == GPIO_PULLUP)
	{
		SET_BIT(SYSCFG->PDPLUP[newIndex], (shiftPin));
	}

	if (pullMsk == GPIO_PULLDOWN)
	{
		SET_BIT(SYSCFG->PDPLDN[newIndex], (shiftPin));
	}

	if (pullMsk == GPIO_NOPULL)
	{
		CLEAR_BIT(SYSCFG->PDPLUP[newIndex], (shiftPin));
		CLEAR_BIT(SYSCFG->PDPLDN[newIndex], (shiftPin));
	}
}


/**
  * @brief  Configure GPIO as Alternative Mode.
  * @param  GPIOx  pointer to a GPIO_Type structure.
  * @param  gpioPin GPIO pin.
  * @param  alternate GPIO alternative function.
  *
  * @retval None.
  */
void DDL_GPIO_Config2AltFunc(GPIO_Type *GPIOx, uint32_t gpioPin, uint32_t alternate)
{
	ASSERT_PARAM(IS_GPIO_ALL_INSTANCE(GPIOx));

	uint32_t index = GPIO_GetPortIndex(GPIOx);
	uint32_t position = GPIO_GetPinIndex(gpioPin);

    MODIFY_REG(SYSCFG->IOMDSEL[index], (SYSCONF_IOMDSEL_PIN_MASK << (position << 1)), (SYSCONF_IOMDSEL_PIN_ALTF << (position << 1)));
	if ( position >= 8 )
	{
		/* Should add alternative function setting for each GPIO */
		MODIFY_REG(SYSCFG->ALTF[2 * index + 1], SYSCONF_AF_MASK << ((position - 8)* 4), alternate << ((position - 8)* 4));
	}
	else
	{
		/* Should add alternative function setting for each GPIO */
		MODIFY_REG(SYSCFG->ALTF[2 * index], SYSCONF_AF_MASK << (position * 4), alternate << (position * 4));
	}
}


/**
  * @brief  Configure GPIO as Analog Mode.
  * @param  GPIOx  pointer to a GPIO_Type structure.
  * @param  gpioPin GPIO pin.
  *
  * @retval None.
  */
void DDL_GPIO_Config2Analog(GPIO_Type *GPIOx, uint32_t gpioPin)
{
	ASSERT_PARAM(IS_GPIO_ALL_INSTANCE(GPIOx));

	uint32_t index = GPIO_GetPortIndex(GPIOx);
	uint32_t position = GPIO_GetPinIndex(gpioPin);

	MODIFY_REG(SYSCFG->IOMDSEL[index], (SYSCONF_IOMDSEL_PIN_MASK << (position << 1)), (SYSCONF_IOMDSEL_PIN_ANAL << (position << 1)));
}


/**
  * @brief  Read out the state of one GPIO Pin with the indication of high or low level.
  * @param  GPIOx     pointer to a GPIO_Type structure.
  *         gpioPin   GPIO pin in one GPIO port. Refer to GPIO_PIN_0 ~ GPIO_PIN_15.
  *
  * @retval GPIO Pin State.
  */
GPIO_PinState_T DDL_GPIO_ReadPin(GPIO_Type *GPIOx, uint16_t gpioPin)
{
	GPIO_PinState_T bitStatus;

	/* Check the parameters */
	ASSERT_PARAM(IS_GPIO_ALL_INSTANCE(GPIOx));
	ASSERT_PARAM(IS_GPIO_PIN(gpioPin));

	/* Get Current GPIO pin Status */
	if((GPIOx->DT & gpioPin) != (uint32_t)GPIO_PIN_RESET)
	{
		bitStatus = GPIO_PIN_SET;
	}
	else
	{
		bitStatus = GPIO_PIN_RESET;
	}

	return bitStatus;
}


/**
  * @brief  Configure one GPIO Pin as high or low level.
  * @param  GPIOx     pointer to a GPIO_Type structure.
  *         gpioPin   GPIO pin in one GPIO port. Refer to GPIO_PIN_0 ~ GPIO_PIN_15.
  *         pinState  the expected high or low level state.
  *
  * @retval None.
  */
void DDL_GPIO_WritePin(GPIO_Type *GPIOx, uint16_t gpioPin, GPIO_PinState_T pinState)
{
	/* Check the parameters */
	ASSERT_PARAM(IS_GPIO_ALL_INSTANCE(GPIOx));
	ASSERT_PARAM(IS_GPIO_PIN(gpioPin));

	/* Output as High-Level */
	if(pinState != GPIO_PIN_RESET)
	{
		GPIOx->DOSR = (uint32_t)gpioPin << 16U;
	}
	else
	{
		GPIOx->DOSR = gpioPin;
	}
}


/**
  * @brief  Toggle one GPIO Pin from high to low or vice versa.
  * @param  GPIOx     pointer to a GPIO_Type structure.
  *         gpioPin   GPIO pin in one GPIO port. Refer to GPIO_PIN_0 ~ GPIO_PIN_15.
  *
  * @retval None.
  */
void DDL_GPIO_TogglePin(GPIO_Type *GPIOx, uint16_t gpioPin)
{
	/* Check the parameters */
	ASSERT_PARAM(IS_GPIO_ALL_INSTANCE(GPIOx));
	ASSERT_PARAM(IS_GPIO_PIN(gpioPin));

	GPIOx->TOGL = (uint32_t)gpioPin;
}


/**
  * @brief  Configure GPIO as Input mode.
  * @param  GPIOx  pointer to a GPIO_Type structure.
  * @param  gpioPin GPIO pin.
  *
  * @retval None.
  */
void DDL_GPIO_PinInputDirectionConfig(GPIO_Type *GPIOx, uint16_t gpioPin)
{
	/* Check the parameters */
	ASSERT_PARAM(IS_GPIO_ALL_INSTANCE(GPIOx));
	ASSERT_PARAM(IS_GPIO_PIN(gpioPin));
	
	GPIOx->OEC = gpioPin;
}


/**
  * @brief  EBUS event triggers GPIO. GPIO could output as High, Low state or
  *         switch between High and Low states.
  *
  *         Caution:  Only one GPIO Pin can be triggered by EBUS event
  *                   in each GPIO Port.
  *
  * @param  GPIOx     pointer to a GPIO_Type structure.
  *         gpioEvt   pointer to a GPIO_Evt_T structure that contains
  *                   configuration information for the given GPIO Pin.
  *  
  * @retval None.
  */
void  DDL_GPIO_EBus_Start(GPIO_Type *GPIOx, GPIO_Evt_T *gpioEvt)
{
	uint8_t i = 0;
	uint32_t temp = 0;

	for (i = 0 ;i < GPIO_NUMBER ;i ++)
	{
		if (gpioEvt->pin & (1 << i))
		{
			break;
		}
	}

	temp = i << GPIO_EVT_PIN_Pos | gpioEvt->evtChn << GPIO_EVT_CHN_Pos | gpioEvt->evtAck;
	WRITE_REG(GPIOx->EVT, temp);
	/* Enable GPIO event */
	SET_BIT(GPIOx->EVT, GPIO_EVT_EN);
}


/**
  * @brief  Configure GPIO as Output mode.
  * @param  GPIOx  pointer to a GPIO_Type structure.
  * @param  gpioPin GPIO pin.
  *
  * @retval None.  
  */
void DDL_GPIO_PinOutputDirectionConfig(GPIO_Type *GPIOx, uint16_t gpioPin)
{
	/* Check the parameters */
	ASSERT_PARAM(IS_GPIO_ALL_INSTANCE(GPIOx));
	ASSERT_PARAM(IS_GPIO_PIN(gpioPin));
	
	GPIOx->OES = gpioPin;
}


static inline uint8_t GPIO_GetBitIndex(uint16_t gpioPin)
{
	uint8_t index = 0;

	while (gpioPin)
	{
		if (gpioPin >> index & 0x01)
		{
			return index;
		}

		index ++;
	}

	return index;
}


/**
  * @brief  Configure GPIO filters
  *
  * @param  GPIOx     pointer to a GPIO_Type structure.
  *         gpioPin   GPIO pin in one GPIO port. Refer to GPIO_PIN_0 ~ GPIO_PIN_15.
  *         pFilter   pointer to a GPIO_InputNF_T structure that contains
  *                   configuration information for the given GPIO Pin.
  *
  * @retval DDL Status.
  */
DDL_Status_T DDL_GPIO_PinNoiseFilterConfig(GPIO_Type *GPIOx, uint16_t gpioPin, GPIO_InputNF_T *pFilter)
{
	uint32_t index = 0;
	uint32_t bit_index = 0;

	ASSERT_PARAM(IS_GPIO_ALL_INSTANCE(GPIOx));
	ASSERT_PARAM(IS_GPIO_PIN(gpioPin));

    index = GPIO_GetPortIndex(GPIOx);
	bit_index = GPIO_GetBitIndex(gpioPin);

	if ( pFilter->enable == PIN_NFLT_ENABLE)
	{
		/* Set GPIO pin Noise Filter Disable */
		SET_BIT(SYSCFG->IOPDF[index / 2], (1 << ((index % 2) * 16 + bit_index)));

		/* Check Noise Filter whether has already been enabled or not, if yes */
		if (READ_BIT(SYSCFG->NF[index / 4], (0x1UL << (index % 4 * 8 + 2))))
		{
			/* Disable Noise Filter function first */
			CLEAR_BIT(SYSCFG->NF[index / 4], (0x1UL << (index % 4 * 8 + 2)));
		}

		/* Then, set GPIO Clock Filter and Enable Noise Filter function finally */
		MODIFY_REG(SYSCFG->NF[index / 4], (0x3UL << (index % 4 * 8)), (pFilter->clock << (index % 4 * 8)));
		SET_BIT(SYSCFG->NF[index / 4],  (0x1UL << (index % 4 * 8 + 2)));
		/* Set GPIO pin Noise Filter Enable */
		CLEAR_BIT(SYSCFG->IOPDF[index / 2], (1 << ((index % 2) * 16 + bit_index)));
	}

    return DDL_OK;
}

#endif /* DDL_GPIO_MODULE_ENABLED */

