/*!
 * @file        apm32f402_403_gpio.c
 *
 * @brief       This file provides all the GPIO firmware functions
 *
 * @version     V1.0.0
 *
 * @date        2024-12-01
 *
 * @attention
 *
 *  Copyright (C) 2024-2025 Geehy Semiconductor
 *
 *  You may not use this file except in compliance with the
 *  GEEHY COPYRIGHT NOTICE (GEEHY SOFTWARE PACKAGE LICENSE).
 *
 *  The program is only for reference, which is distributed in the hope
 *  that it will be useful and instructional for customers to develop
 *  their software. Unless required by applicable law or agreed to in
 *  writing, the program is distributed on an "AS IS" BASIS, WITHOUT
 *  ANY WARRANTY OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the GEEHY SOFTWARE PACKAGE LICENSE for the governing permissions
 *  and limitations under the License.
 */

/* Includes */
#include "apm32f402_403_gpio.h"
#include "apm32f402_403_rcm.h"

/** @addtogroup APM32F402_403_StdPeriphDriver
  @{
*/

/** @addtogroup GPIO_Driver GPIO Driver
  @{
*/

/** @defgroup GPIO_Functions Functions
  @{
*/

/*!
 * @brief     Reset GPIO peripheral registers to their default reset values
 *
 * @param     port: Select the GPIO port.
 *                  This parameter can be one of GPIOx( x can be from A to D).
 *
 * @retval    None
 */
void GPIO_Reset(GPIO_T* port)
{
    RCM_APB2_PERIPH_T APB2Periph = RCM_APB2_PERIPH_GPIOA;

    if (port == GPIOA)
    {
        APB2Periph = RCM_APB2_PERIPH_GPIOA;
    }
    else if (port == GPIOB)
    {
        APB2Periph = RCM_APB2_PERIPH_GPIOB;
    }
    else if (port == GPIOC)
    {
        APB2Periph = RCM_APB2_PERIPH_GPIOC;
    }
    else if (port == GPIOD)
    {
        APB2Periph = RCM_APB2_PERIPH_GPIOD;
    }

    RCM_EnableAPB2PeriphReset(APB2Periph);
    RCM_DisableAPB2PeriphReset(APB2Periph);
}

/*!
 * @brief     Reset Alternate Functions registers to their default reset values
 *
 * @param     None
 *
 * @retval    None
 */
void GPIO_AFIOReset(void)
{
    RCM_EnableAPB2PeriphReset(RCM_APB2_PERIPH_AFIO);
    RCM_DisableAPB2PeriphReset(RCM_APB2_PERIPH_AFIO);
}

/*!
 * @brief     Config the GPIO peripheral according to the specified parameters in the gpioConfig
 *
 * @param     port: Select the GPIO port.
 *                  This parameter can be one of GPIOx( x can be from A to D).
 *
 * @param     gpioConfig: pointer to a GPIO_Config_T structure
 *
 * @retval    None
 */
void GPIO_Config(GPIO_T* port, GPIO_Config_T* gpioConfig)
{
    uint8_t i;
    uint32_t mode;
    uint32_t CR;
    uint32_t temp;
    uint32_t shift;

    mode = gpioConfig->mode & 0x0f;

    if (gpioConfig->mode & 0x80)
    {
        mode |= gpioConfig->speed;
    }

    if (gpioConfig->pin & 0xff)
    {
        CR = port->CFGLOW;

        for (i = 0, shift = 0x01; i < 8; i++, shift <<= 1)
        {
            if (gpioConfig->pin & shift)
            {
                temp = i << 2;
                CR &= (uint32_t)~(0x0f << temp);
                CR |= mode << temp;

                if (gpioConfig->mode == GPIO_MODE_IN_PD)
                {
                    port->BC = shift;
                }
                else if (gpioConfig->mode == GPIO_MODE_IN_PU)
                {
                    port->BSC = shift;
                }
            }
        }

        port->CFGLOW = CR;
    }

    if (gpioConfig->pin & 0xff00)
    {
        CR = port->CFGHIG;

        for (i = 8, shift = 0x100; i < 16; i++, shift <<= 1)
        {
            if (gpioConfig->pin & shift)
            {
                temp = (i - 8) << 2;
                CR &= (uint32_t)~(0x0f << temp);
                CR |= mode << temp;

                if (gpioConfig->mode == GPIO_MODE_IN_PD)
                {
                    port->BC = shift;
                }
                else if (gpioConfig->mode == GPIO_MODE_IN_PU)
                {
                    port->BSC = shift;
                }
            }
        }

        port->CFGHIG = CR;
    }
}

/*!
 * @brief     Fills each gpioConfig member with its default value.
 *
 * @param     gpioConfig : pointer to a GPIO_Config_T structure which will be initialized.
 *
 * @retval    None
 */
void GPIO_ConfigStructInit(GPIO_Config_T* gpioConfig)
{
    gpioConfig->pin  = GPIO_PIN_ALL;
    gpioConfig->speed = GPIO_SPEED_2MHz;
    gpioConfig->mode = GPIO_MODE_IN_FLOATING;
}

/*!
 * @brief     Reads the specified input port pin
 *
 * @param     port: Select the GPIO port.
 *                  This parameter can be one of GPIOx( x can be from A to D).
 *
 * @param     pin : specifies pin to read.
 *                  This parameter can be one of GPIO_PIN_x( x can be from 0 to 15).
 *
 * @retval    The input port pin value
 */
uint8_t GPIO_ReadInputBit(GPIO_T* port, uint16_t pin)
{
    uint8_t ret;

    ret = (port->IDATA & pin) ?  BIT_SET : BIT_RESET;

    return ret;
}

/*!
 * @brief     Reads the specified GPIO input data port
 *
 * @param     port: Select the GPIO port.
 *                  This parameter can be one of GPIOx( x can be from A to D).
 *
 * @retval    GPIO input data port value
 */
uint16_t GPIO_ReadInputPort(GPIO_T* port)
{
    return ((uint16_t)port->IDATA);
}

/*!
 * @brief     Reads the specified output data port bit
 *
 * @param     port: Select the GPIO port.
 *                  This parameter can be one of GPIOx( x can be from A to D).
 *
 * @param     pin : specifies pin to read.
 *                  This parameter can be one of GPIO_PIN_x( x can be from 0 to 15).
 *
 * @retval    The output port pin value
 */
uint8_t GPIO_ReadOutputBit(GPIO_T* port, uint16_t pin)
{

    uint8_t ret;

    ret = (port->ODATA & pin) ? BIT_SET : BIT_RESET;

    return ret;
}

/*!
 * @brief     Reads the specified GPIO output data port
 *
 * @param     port: Select the GPIO port.
 *                  This parameter can be one of GPIOx( x can be from A to D).
 *
 * @retval    output data port value
 */
uint16_t GPIO_ReadOutputPort(GPIO_T* port)
{
    return ((uint16_t)port->ODATA);
}

/*!
 * @brief     Sets the selected data port bits
 *
 * @param     port: Select the GPIO port.
 *                  This parameter can be one of GPIOx( x can be from A to D).
 *
 * @param     pin : specifies pin to be written.
 *                  This parameter can be any combination of GPIO_PIN_x( x can be from 0 to 15).
 *
 * @retval    None
 */
void GPIO_SetBit(GPIO_T* port, uint16_t pin)
{
    port->BSC = (uint32_t)pin;
}

/*!
 * @brief     Clears the selected data port bits
 *
 * @param     port: Select the GPIO port.
 *                  This parameter can be one of GPIOx( x can be from A to D).
 *
 * @param     pin : specifies pin to be cleared.
 *                  This parameter can be any combination of GPIO_PIN_x( x can be from 0 to 15).
 *
 * @retval    None
 */
void GPIO_ResetBit(GPIO_T* port, uint16_t pin)
{
    port->BC = (uint32_t)pin;
}

/*!
 * @brief     Writes data to the specified GPIO data port bit
 *
 * @param     port: Select the GPIO port.
 *                  This parameter can be one of GPIOx( x can be from A to D).
 *
 * @param     pin : Select specifies pin.
 *                  This parameter can be one of GPIO_PIN_x( x can be from 0 to 15).
 *
 *
 * @param     bitVal : specifies the value to be written to the port output data register
 *                     This parameter can be one of the following values:
 *                       @arg BIT_RESET: Reset the port pin
 *                       @arg BIT_SET  : Set the port pin
 *
 * @retval    None
 */
void GPIO_WriteBitValue(GPIO_T* port, uint16_t pin, uint8_t bitVal)
{
    if (bitVal != BIT_RESET)
    {
        port->BSC = pin;
    }
    else
    {
        port->BC = pin ;
    }
}

/*!
 * @brief     Writes data to the specified GPIO data port
 *
 * @param     port: Select the GPIO port.
 *                  This parameter can be one of GPIOx( x can be from A to D).
 *
 * @param     portValue : specifies the value to be written to the port output data register.
 *
 * @retval    None
 */
void GPIO_WriteOutputPort(GPIO_T* port, uint16_t portValue)
{
    port->ODATA = (uint32_t)portValue;
}

/*!
 * @brief     Locks GPIO Pins configuration registers
 *
 * @param     port: Select the GPIO port.
 *                  This parameter can be one of GPIOx( x can be from A to D).
 *
 * @param     pin : Select specifies pin.
 *                  This parameter can be any combination of GPIO_PIN_x( x can be from 0 to 15).
 *
 * @retval    None
 */
void GPIO_ConfigPinLock(GPIO_T* port, uint16_t pin)
{
    uint32_t val = 0x00010000;

    val  |= pin;
    /* Set LCKK bit */
    port->LOCK = val ;
    /* Reset LCKK bit */
    port->LOCK =  pin;
    /* Set LCKK bit */
    port->LOCK = val;
    /* Read LCKK bit*/
    val = port->LOCK;
    /* Read LCKK bit*/
    val = port->LOCK;
}

/*!
 * @brief     Selects the GPIO pin used as Event output
 *
 * @param     portSource : selects the GPIO port to be used as source for Event output.
 *                         This parameter can be one of GPIO_PORT_SOURCE_x( x can be from A to D).
 *
 * @param     pinSource   specifies the pin for the Event output
 *                  This parameter can be GPIO_PIN_SOURCE_x( x can be from 0 to 15).
 *
 * @retval    None
 */
void GPIO_ConfigEventOutput(GPIO_PORT_SOURCE_T portSource, GPIO_PIN_SOURCE_T pinSource)
{
    AFIO->EVCTRL_B.PORTSEL =  portSource;
    AFIO->EVCTRL_B.PINSEL = pinSource;
}

/*!
 * @brief     Enables the Event Output
 *
 * @param     None
 *
 * @retval    None
 */
void GPIO_EnableEventOutput(void)
{
    AFIO->EVCTRL_B.EVOEN = BIT_SET;
}

/*!
 * @brief     Disable the Event Output
 *
 * @param     None
 *
 * @retval    None
 */
void GPIO_DisableEventOutput(void)
{
    AFIO->EVCTRL_B.EVOEN = BIT_RESET;
}

/*!
 * @brief     Changes the mapping of the specified pin
 *
 * @param     remap : selects the pin to remap
 *                    This parameter can be one of the following values:
 *                    @arg GPIO_NO_REMAP_SPI1         : No SPI1 Alternate Function mapping
 *                    @arg GPIO_REMAP_SPI1            : SPI1 Alternate Function mapping
 *                    @arg GPIO_NO_REMAP_I2C1         : No I2C1 Alternate Function mapping
 *                    @arg GPIO_REMAP_I2C1            : I2C1 Alternate Function mapping
 *                    @arg GPIO_NO_REMAP_USART1       : No USART1 Alternate Function mapping
 *                    @arg GPIO_REMAP_USART1          : USART1 Alternate Function mapping
 *                    @arg GPIO_NO_REMAP_USART2       : No USART2 Alternate Function mapping
 *                    @arg GPIO_REMAP_USART2          : USART2 Alternate Function mapping
 *                    @arg GPIO_NO_REMAP_USART3       : No USART3 Partial Alternate Function mapping
 *                    @arg GPIO_PARTIAL_REMAP_USART3  : USART3 Partial Alternate Function mapping
 *                    @arg GPIO_NO_REMAP_TMR1         : No TIM1 Partial Alternate Function mapping
 *                    @arg GPIO_PARTIAL_REMAP_TMR1    : TIM1 Partial Alternate Function mapping
 *                    @arg GPIO_NO_REMAP1_TMR2        : No TIM2 Partial1 Alternate Function mapping
 *                    @arg GPIO_PARTIAL_REMAP1_TMR2   : TIM2 Partial1 Alternate Function mapping
 *                    @arg GPIO_PARTIAL_REMAP2_TMR2   : TIM2 Partial2 Alternate Function mapping
 *                    @arg GPIO_FULL_REMAP_TMR2       : TIM2 Full Alternate Function mapping
 *                    @arg GPIO_NO_REMAP_TMR3         : No TIM3 Partial Alternate Function mapping
 *                    @arg GPIO_PARTIAL_REMAP_TMR3    : TIM3 Partial Alternate Function mapping
 *                    @arg GPIO_FULL_REMAP_TMR3       : TIM3 Full Alternate Function mapping
 *                    @arg GPIO_NO_REMAP_TMR4         : No TIM4 Alternate Function mapping
 *                    @arg GPIO_NO_REMAP_CAN1         : No CAN1 Alternate Function mapping
 *                    @arg GPIO_REMAP1_CAN1           : CAN1 Alternate Function mapping
 *                    @arg GPIO_REMAP2_CAN1           : CAN1 Alternate Function mapping
 *                    @arg GPIO_NO_REMAP_PD01         : No PD01 Alternate Function mapping
 *                    @arg GPIO_REMAP_PD01            : PD01 Alternate Function mapping
 *                    @arg GPIO_NO_REMAP_TMR5CH4_LSI  : No LSI connected to TIM5 Channel4 input capture for calibration
 *                    @arg GPIO_REMAP_TMR5CH4_LSI     : LSI connected to TIM5 Channel4 input capture for calibration
 *                    @arg GPIO_NO_REMAP_ADC1_ETRGINJ : No ADC1 External Trigger Injected Conversion remapping
 *                    @arg GPIO_REMAP_ADC1_ETRGINJ    : ADC1 External Trigger Injected Conversion remapping
 *                    @arg GPIO_NO_REMAP_ADC1_ETRGREG : No ADC1 External Trigger Regular Conversion remapping
 *                    @arg GPIO_REMAP_ADC1_ETRGREG    : ADC1 External Trigger Regular Conversion remapping
 *                    @arg GPIO_NO_REMAP_ADC2_ETRGINJ : No ADC2 External Trigger Injected Conversion remapping
 *                    @arg GPIO_REMAP_ADC2_ETRGINJ    : ADC2 External Trigger Injected Conversion remapping
 *                    @arg GPIO_NO_REMAP_ADC2_ETRGREG : No ADC2 External Trigger Regular Conversion remapping
 *                    @arg GPIO_REMAP_ADC2_ETRGREG    : ADC2 External Trigger Regular Conversion remapping
 *                    @arg GPIO_NO_REMAP_CAN2         : No CAN2 Alternate Function mapping
 *                    @arg GPIO_REMAP_CAN2            : CAN2 Alternate Function mapping
 *                    @arg GPIO_NO_REMAP_SWJ          : Full SWJ Enabled (JTAG-DP + SW-DP)
 *                    @arg GPIO_REMAP_SWJ_NOJTRST     : Full SWJ Enabled (JTAG-DP + SW-DP) but without JTRST
 *                    @arg GPIO_REMAP_SWJ_JTAGDISABLE : JTAG-DP Disabled and SW-DP Enabled
 *                    @arg GPIO_REMAP_SWJ_DISABLE     : Full SWJ Disabled (JTAG-DP + SW-DP)
 *
 * @retval    When you use GPIO_REMAP_CAN2, you must put this function last of all other ConfigPinRemap Function.
 * 
 * @note      If the GPIO is related to the SWJ pins(PB3, PB4, PA13, PA14, PA15), the SWJCFG bit field must be configured first to change the 
 *            IO to a normal GPIO before remapping.
 */
void GPIO_ConfigPinRemap(GPIO_REMAP_T remap)
{
    uint32_t val, mask, bitOffset;
    uint32_t regVal;

    val = remap & 0x0f;
    mask = (remap >> 4) & 0x0f;
    bitOffset = (remap >> 8) & 0xff;
 
    regVal = AFIO->REMAP1;

    if (remap >> 8 == 0x18)
    {
        regVal &= 0xF0FFFFFF;
        AFIO->REMAP1 &= 0xF0FFFFFF;
    }
    else
    {
        regVal |= 0x0F000000;
    }

    mask <<= bitOffset;
    regVal &= (uint32_t)~mask;
    val <<= bitOffset;
    regVal |= val;

    AFIO->REMAP1 = regVal;
}

/*!
 * @brief     Selects the GPIO pin used as EINT Line
 *
 * @param     portSource : selects the GPIO port to be used as source for EINT line.
 *                         This parameter can be one of GPIO_PORT_SOURCE_x( x can be from A to D).
 *
 * @param     pinSource : Specifies the EINT line to be configured.
 *                        This parameter can be GPIO_PIN_SOURCE_x( x can be from 0 to 15).
 *
 * @retval    None
 */
void GPIO_ConfigEINTLine(GPIO_PORT_SOURCE_T portSource, GPIO_PIN_SOURCE_T pinSource)
{
    uint32_t shift;

    if (pinSource <= GPIO_PIN_SOURCE_3)
    {
        shift = pinSource << 2;
        AFIO->EINTSEL1 &= (uint32_t)~(0x0f << shift);
        AFIO->EINTSEL1 |=  portSource << shift;
    }

    else if (pinSource <= GPIO_PIN_SOURCE_7)
    {
        shift = (pinSource - GPIO_PIN_SOURCE_4) << 2;
        AFIO->EINTSEL2 &= (uint32_t)~(0x0f << shift);
        AFIO->EINTSEL2 |=  portSource << shift;
    }

    else if (pinSource <= GPIO_PIN_SOURCE_11)
    {
        shift = (pinSource - GPIO_PIN_SOURCE_8) << 2;
        AFIO->EINTSEL3 &= (uint32_t)~(0x0f << shift);
        AFIO->EINTSEL3 |=  portSource << shift;
    }

    else if (pinSource <= GPIO_PIN_SOURCE_15)
    {
        shift = (pinSource - GPIO_PIN_SOURCE_12) << 2;
        AFIO->EINTSEL4 &= (uint32_t)~(0x0f << shift);
        AFIO->EINTSEL4 |=  portSource << shift;
    }
}

/**@} end of group GPIO_Functions*/
/**@} end of group GPIO_Driver*/
/**@} end of group APM32F402_403_StdPeriphDriver*/
