/*!
    \file  gd32f30x_gpio.c
    \brief GPIO driver
*/

/*
    Copyright (C) 2017 GigaDevice

    2017-02-10, V1.0.1, firmware for GD32F30x
*/

#include "gd32f30x_gpio.h"

#define AFIO_EXTI_SOURCE_FIELDS            ((uint8_t)0x04U)         /*!< select AFIO exti source registers */
#define LSB_16BIT_MASK                     ((uint16_t)0xFFFFU)      /*!< LSB 16-bit mask */
#define PCF_POSITION_MASK                  ((uint32_t)0x000F0000U)  /*!< AFIO_PCF register position mask */
#define PCF_SWJCFG_MASK                    ((uint32_t)0xF0FFFFFFU)  /*!< AFIO_PCF register SWJCFG mask */
#define PCF_LOCATION1_MASK                 ((uint32_t)0x00200000U)  /*!< AFIO_PCF register location1 mask */
#define PCF_LOCATION2_MASK                 ((uint32_t)0x00100000U)  /*!< AFIO_PCF register location2 mask */
#define AFIO_PCF1_FIELDS                   ((uint32_t)0x80000000U)  /*!< select AFIO_PCF1 register */

/*!
    \brief      reset GPIO port
    \param[in]  gpio_periph: GPIOx(x = A,B,C,D,E,F,G)
    \param[out] none
    \retval     none
*/
void gpio_deinit(uint32_t gpio_periph)
{
    switch(gpio_periph){
    case GPIOA:
        /* reset GPIOA */
        rcu_periph_reset_enable(RCU_GPIOARST);
        rcu_periph_reset_disable(RCU_GPIOARST);
        break;
    case GPIOB:
        /* reset GPIOB */
        rcu_periph_reset_enable(RCU_GPIOBRST);
        rcu_periph_reset_disable(RCU_GPIOBRST);
        break;
    case GPIOC:
        /* reset GPIOC */
        rcu_periph_reset_enable(RCU_GPIOCRST);
        rcu_periph_reset_disable(RCU_GPIOCRST);
        break;
    case GPIOD:
        /* reset GPIOD */
        rcu_periph_reset_enable(RCU_GPIODRST);
        rcu_periph_reset_disable(RCU_GPIODRST);
        break;
    case GPIOE:
        /* reset GPIOE */
        rcu_periph_reset_enable(RCU_GPIOERST);
        rcu_periph_reset_disable(RCU_GPIOERST);
        break;
    case GPIOF:
        /* reset GPIOF */
        rcu_periph_reset_enable(RCU_GPIOFRST);
        rcu_periph_reset_disable(RCU_GPIOFRST);
        break;
    case GPIOG:
        /* reset GPIOG */
        rcu_periph_reset_enable(RCU_GPIOGRST);
        rcu_periph_reset_disable(RCU_GPIOGRST);
        break;
    default:
        break;
    }
}

/*!
    \brief      reset alternate function I/O(AFIO)
    \param[in]  none
    \param[out] none
    \retval     none
*/
void gpio_afio_deinit(void)
{
    rcu_periph_reset_enable(RCU_AFRST);
    rcu_periph_reset_disable(RCU_AFRST);
}

/*!
    \brief      GPIO parameter initialization
    \param[in]  gpio_periph: GPIOx(x = A,B,C,D,E,F,G) 
    \param[in]  mode: gpio pin mode
      \arg        GPIO_MODE_AIN: analog input mode
      \arg        GPIO_MODE_IN_FLOATING: floating input mode
      \arg        GPIO_MODE_IPD: pull-down input mode
      \arg        GPIO_MODE_IPU: pull-up input mode
      \arg        GPIO_MODE_OUT_OD: GPIO output with open-drain
      \arg        GPIO_MODE_OUT_PP: GPIO output with push-pull
      \arg        GPIO_MODE_AF_OD: AFIO output with open-drain
      \arg        GPIO_MODE_AF_PP: AFIO output with push-pull
    \param[in]  speed: gpio output max speed value
      \arg        GPIO_OSPEED_10MHZ: output max speed 10MHz
      \arg        GPIO_OSPEED_2MHZ: output max speed 2MHz
      \arg        GPIO_OSPEED_50MHZ: output max speed 50MHz
      \arg        GPIO_OSPEED_MAX: output max speed more than 50MHz
    \param[in]  pin: GPIO_PIN_x(x=0..15), GPIO_PIN_ALL
    \param[out] none
    \retval     none
*/
void gpio_init(uint32_t gpio_periph, uint32_t mode, uint32_t speed, uint32_t pin)
{
    uint16_t i;
    uint32_t temp_mode = 0U;
    uint32_t reg = 0U;

    /* GPIO mode configuration */
    temp_mode = (uint32_t)(mode & ((uint32_t)0x0FU));
    
    /* GPIO speed configuration */
    if(((uint32_t)0x00U) != ((uint32_t)mode & ((uint32_t)0x10U))){
        /* output mode max speed */
        if(GPIO_OSPEED_MAX == (uint32_t)speed){
            temp_mode |= (uint32_t)0x03U;
            /* set the corresponding SPD bit */
            GPIOx_SPD(gpio_periph) |= (uint32_t)pin ;
        }else{
            /* output mode max speed:10MHz,2MHz,50MHz */
            temp_mode |= (uint32_t)speed;
        }
    }

    /* configure the eight low port pins with GPIO_CTL0 */
    for(i = 0U;i < 8U;i++){
        if((1U << i) & pin){
            reg = GPIO_CTL0(gpio_periph);
            
            /* clear the specified pin mode bits */
            reg &= ~GPIO_MODE_MASK(i);
            /* set the specified pin mode bits */
            reg |= GPIO_MODE_SET(i, temp_mode);
            
            /* set IPD or IPU */
            if(GPIO_MODE_IPD == mode){
                /* reset the corresponding OCTL bit */
                GPIO_BC(gpio_periph) = (uint32_t)pin;
            }else{
                /* set the corresponding OCTL bit */
                if(GPIO_MODE_IPU == mode){
                    GPIO_BOP(gpio_periph) = (uint32_t)pin;
                }
            }
            /* set GPIO_CTL0 register */
            GPIO_CTL0(gpio_periph) = reg;
        }
    }
    /* configure the eight high port pins with GPIO_CTL1 */
    for(i = 8U;i < 16U;i++){
        if((1U << i) & pin){
            reg = GPIO_CTL1(gpio_periph);
            
            /* clear the specified pin mode bits */
            reg &= ~GPIO_MODE_MASK(i - 8U);
            /* set the specified pin mode bits */
            reg |= GPIO_MODE_SET(i - 8U, temp_mode);
            
            /* set IPD or IPU */
            if(GPIO_MODE_IPD == mode){
                /* reset the corresponding OCTL bit */
                GPIO_BC(gpio_periph) = (uint32_t)pin;
            }else{
                /* set the corresponding OCTL bit */
                if(GPIO_MODE_IPU == mode){
                    GPIO_BOP(gpio_periph) = (uint32_t)pin;
                }
            }
            /* set GPIO_CTL1 register */
            GPIO_CTL1(gpio_periph) = reg;
        }
    }
}

/*!
    \brief      set GPIO pin
    \param[in]  gpio_periph: GPIOx(x = A,B,C,D,E,F,G) 
    \param[in]  pin: GPIO_PIN_x(x=0..15), GPIO_PIN_ALL
    \param[out] none
    \retval     none
*/
void gpio_bit_set(uint32_t gpio_periph,uint32_t pin)
{
    GPIO_BOP(gpio_periph) = (uint32_t)pin;
}

/*!
    \brief      reset GPIO pin
    \param[in]  gpio_periph: GPIOx(x = A,B,C,D,E,F,G) 
    \param[in]  pin: GPIO_PIN_x(x=0..15), GPIO_PIN_ALL
    \param[out] none
    \retval     none
*/
void gpio_bit_reset(uint32_t gpio_periph,uint32_t pin)
{
    GPIO_BC(gpio_periph) = (uint32_t)pin;
}

/*!
    \brief      write data to the specified GPIO pin
    \param[in]  gpio_periph: GPIOx(x = A,B,C,D,E,F,G) 
    \param[in]  pin: GPIO_PIN_x(x=0..15), GPIO_PIN_ALL
    \param[in]  bit_value: SET or RESET
      \arg        RESET: clear the port pin
      \arg        SET: set the port pin
    \param[out] none
    \retval     none
*/
void gpio_bit_write(uint32_t gpio_periph,uint32_t pin,bit_status bit_value)
{
    if(RESET != bit_value){
        GPIO_BOP(gpio_periph) = (uint32_t)pin;
    }else{
        GPIO_BC(gpio_periph) = (uint32_t)pin;
    }
}

/*!
    \brief      write data to the specified GPIO port
    \param[in]  gpio_periph: GPIOx(x = A,B,C,D,E,F,G) 
    \param[in]  data: specify the value to be written to the port output data register
    \param[out] none
    \retval     none
*/
void gpio_port_write(uint32_t gpio_periph,uint16_t data)
{
    GPIO_OCTL(gpio_periph) = (uint32_t)data;
}

/*!
    \brief      get GPIO pin input status
    \param[in]  gpio_periph: GPIOx(x = A,B,C,D,E,F,G) 
    \param[in]  pin: GPIO_PIN_x(x=0..15), GPIO_PIN_ALL
    \param[out] none
    \retval     input status of gpio pin: SET or RESET
*/
FlagStatus gpio_input_bit_get(uint32_t gpio_periph,uint32_t pin)
{
    if((uint32_t)RESET != (GPIO_ISTAT(gpio_periph)&(pin))){
        return SET; 
    }else{
        return RESET;
    }
}

/*!
    \brief      get GPIO port input status
    \param[in]  gpio_periph: GPIOx(x = A,B,C,D,E,F,G) 
    \param[out] none
    \retval     input status of gpio all pins
*/
uint16_t gpio_input_port_get(uint32_t gpio_periph)
{
    return (uint16_t)(GPIO_ISTAT(gpio_periph));
}

/*!
    \brief      get GPIO pin output status
    \param[in]  gpio_periph: GPIOx(x = A,B,C,D,E,F,G) 
    \param[in]  pin: GPIO_PIN_x(x=0..15), GPIO_PIN_ALL
    \param[out] none
    \retval     output status of gpio pin: SET or RESET
*/
FlagStatus gpio_output_bit_get(uint32_t gpio_periph,uint32_t pin)
{
    if((uint32_t)RESET !=(GPIO_OCTL(gpio_periph)&(pin))){
        return SET;
    }else{
        return RESET;
    }
}

/*!
    \brief      get GPIO port output status
    \param[in]  gpio_periph: GPIOx(x = A,B,C,D,E,F,G) 
    \param[out] none
    \retval     output status of gpio all pins
*/
uint16_t gpio_output_port_get(uint32_t gpio_periph)
{
    return ((uint16_t)GPIO_OCTL(gpio_periph));
}

/*!
    \brief      lock GPIO pin
    \param[in]  gpio_periph: GPIOx(x = A,B,C,D,E,F,G) 
    \param[in]  pin: GPIO_PIN_x(x=0..15), GPIO_PIN_ALL
    \param[out] none
    \retval     none
*/
void gpio_pin_lock(uint32_t gpio_periph,uint32_t pin)
{
    uint32_t lock = 0x00010000U;
    lock |= pin;

    /* lock key writing sequence: write 1 -> write 0 -> write 1 -> read 0 -> read 1 */
    GPIO_LOCK(gpio_periph) = (uint32_t)lock;
    GPIO_LOCK(gpio_periph) = (uint32_t)pin;
    GPIO_LOCK(gpio_periph) = (uint32_t)lock;
    lock = GPIO_LOCK(gpio_periph);
    lock = GPIO_LOCK(gpio_periph);
}

/*!
    \brief      configure GPIO pin event output
    \param[in]  output_port: gpio event output port
      \arg        GPIO_EVENT_PORT_GPIOA: event output port A
      \arg        GPIO_EVENT_PORT_GPIOB: event output port B
      \arg        GPIO_EVENT_PORT_GPIOC: event output port C
      \arg        GPIO_EVENT_PORT_GPIOD: event output port D
      \arg        GPIO_EVENT_PORT_GPIOE: event output port E
    \param[in]  output_pin: GPIO_EVENT_PIN_x(x=0..15)
    \param[out] none
    \retval     none
*/
void gpio_event_output_config(uint8_t output_port, uint8_t output_pin)
{
    uint32_t reg = 0U;
    reg = AFIO_EC;
    
    /* clear AFIO_EC_PORT and AFIO_EC_PIN bits */
    reg &= (uint32_t)(~(AFIO_EC_PORT|AFIO_EC_PIN));
    
    reg |= (uint32_t)((uint32_t)output_port << 0x04U);
    reg |= (uint32_t)output_pin;
    
    AFIO_EC = reg;
}

/*!
    \brief      enable GPIO pin event output
    \param[in]  none
    \param[out] none
    \retval     none
*/
void gpio_event_output_enable(void)
{
    AFIO_EC |= AFIO_EC_EOE;
}

/*!
    \brief      disable GPIO pin event output
    \param[in]  none
    \param[out] none
    \retval     none
*/
void gpio_event_output_disable(void)
{
    AFIO_EC &= (uint32_t)(~AFIO_EC_EOE);
}

/*!
    \brief      select GPIO pin exti sources
    \param[in]  output_port: gpio event output port
      \arg        GPIO_PORT_SOURCE_GPIOA: output port source A
      \arg        GPIO_PORT_SOURCE_GPIOB: output port source B
      \arg        GPIO_PORT_SOURCE_GPIOC: output port source C
      \arg        GPIO_PORT_SOURCE_GPIOD: output port source D
      \arg        GPIO_PORT_SOURCE_GPIOE: output port source E 
      \arg        GPIO_PORT_SOURCE_GPIOF: output port source F
      \arg        GPIO_PORT_SOURCE_GPIOG: output port source G
    \param[in]  output_pin: GPIO_PIN_SOURCE_x(x=0..15)
    \param[out] none
    \retval     none
*/
void gpio_exti_source_select(uint8_t output_port, uint8_t output_pin)
{
    uint32_t source = 0U;
    source = ((uint32_t)0x0FU) << (AFIO_EXTI_SOURCE_FIELDS * (output_pin & (uint8_t)0x03U));

    /* select EXTI sources */
    if(GPIO_PIN_SOURCE_4 > output_pin){
        /* select EXTI0/EXTI1/EXTI2/EXTI3 */
        AFIO_EXTISS0 &= ~source;
        AFIO_EXTISS0 |= (((uint32_t)output_port) << (AFIO_EXTI_SOURCE_FIELDS * (output_pin & (uint8_t)0x03U)));
    }else if(GPIO_PIN_SOURCE_8 > output_pin){
        /* select EXTI4/EXTI5/EXTI6/EXTI7 */
        AFIO_EXTISS1 &= ~source;
        AFIO_EXTISS1 |= (((uint32_t)output_port) << (AFIO_EXTI_SOURCE_FIELDS * (output_pin & (uint8_t)0x03U)));
    }else if(GPIO_PIN_SOURCE_12 > output_pin){
        /* select EXTI8/EXTI9/EXTI10/EXTI11 */
        AFIO_EXTISS2 &= ~source;
        AFIO_EXTISS2 |= (((uint32_t)output_port) << (AFIO_EXTI_SOURCE_FIELDS * (output_pin & (uint8_t)0x03U)));
    }else{
        /* select EXTI12/EXTI13/EXTI14/EXTI15 */
        AFIO_EXTISS3 &= ~source;
        AFIO_EXTISS3 |= (((uint32_t)output_port) << (AFIO_EXTI_SOURCE_FIELDS * (output_pin & (uint8_t)0x03U)));
    }
}

#ifdef GD32F30X_CL
/*!
    \brief      select ethernet MII or RMII PHY
    \param[in]  enet_sel: ethernet MII or RMII PHY selection
      \arg        GPIO_ENET_PHY_MII: configure ethernet MAC for connection with an MII PHY
      \arg        GPIO_ENET_PHY_RMII: configure ethernet MAC for connection with an RMII PHY
    \param[out] none
    \retval     none
*/
void gpio_ethernet_phy_select(uint32_t enet_sel)
{
    /* clear AFIO_PCF0_ENET_PHY_SEL bit */
    AFIO_PCF0 &= (uint32_t)(~AFIO_PCF0_ENET_PHY_SEL);

    /* select MII or RMII PHY */
    AFIO_PCF0 |= (uint32_t)enet_sel;
}
#endif /* GD32F30X_CL */

/*!
    \brief      configure GPIO pin remap
    \param[in]  gpio_remap: select the pin to remap
      \arg        GPIO_SPI0_REMAP: SPI0 remapping
      \arg        GPIO_I2C0_REMAP: I2C0 remapping
      \arg        GPIO_USART0_REMAP: USART0 remapping
      \arg        GPIO_USART1_REMAP: USART1 remapping
      \arg        GPIO_USART2_PARTIAL_REMAP: USART2 partial remapping
      \arg        GPIO_USART2_FULL_REMAP: USART2 full remapping
      \arg        GPIO_TIMER0_PARTIAL_REMAP: TIMER0 partial remapping 
      \arg        GPIO_TIMER0_FULL_REMAP: TIMER0 full remapping
      \arg        GPIO_TIMER1_PARTIAL_REMAP1: TIMER1 partial remapping
      \arg        GPIO_TIMER1_PARTIAL_REMAP2: TIMER1 partial remapping
      \arg        GPIO_TIMER1_FULL_REMAP: TIMER1 full remapping
      \arg        GPIO_TIMER2_PARTIAL_REMAP: TIMER2 partial remapping
      \arg        GPIO_TIMER2_FULL_REMAP: TIMER2 full remapping
      \arg        GPIO_TIMER3_REMAP: TIMER3 remapping
      \arg        GPIO_CAN_PARTIAL_REMAP: CAN partial remapping(only for GD32F30X_HD devices and GD32F30X_XD devices)
      \arg        GPIO_CAN_FULL_REMAP: CAN full remapping(only for GD32F30X_HD devices and GD32F30X_XD devices)
      \arg        GPIO_CAN0_PARTIAL_REMAP: CAN0 partial remapping(only for GD32F30X_CL devices)
      \arg        GPIO_CAN0_FULL_REMAP: CAN0 full remapping(only for GD32F30X_CL devices)
      \arg        GPIO_PD01_REMAP: PD01 remapping
      \arg        GPIO_TIMER4CH3_IREMAP: TIMER4 channel3 internal remapping(only for GD32F30X_CL devices and GD32F30X_HD devices)
      \arg        GPIO_ADC0_ETRGINS_REMAP: ADC0 external trigger inserted conversion remapping(only for GD32F30X_HD devices and GD32F30X_XD devices)
      \arg        GPIO_ADC0_ETRGREG_REMAP: ADC0 external trigger regular conversion remapping(only for GD32F30X_HD devices and GD32F30X_XD devices)
      \arg        GPIO_ADC1_ETRGINS_REMAP: ADC1 external trigger inserted conversion remapping(only for GD32F30X_HD devices and GD32F30X_XD devices)
      \arg        GPIO_ADC1_ETRGREG_REMAP: ADC1 external trigger regular conversion remapping(only for GD32F30X_HD devices and GD32F30X_XD devices)
      \arg        GPIO_ENET_REMAP: ENET remapping(only for GD32F30X_CL devices) 
      \arg        GPIO_CAN1_REMAP: CAN1 remapping(only for GD32F30X_CL devices)
      \arg        GPIO_SWJ_NONJTRST_REMAP: full SWJ(JTAG-DP + SW-DP),but without NJTRST
      \arg        GPIO_SWJ_SWDPENABLE_REMAP: JTAG-DP disabled and SW-DP enabled
      \arg        GPIO_SWJ_DISABLE_REMAP: JTAG-DP disabled and SW-DP disabled
      \arg        GPIO_SPI2_REMAP: SPI2 remapping(only for GD32F30X_CL devices) 
      \arg        GPIO_TIMER1ITR0_REMAP: TIMER1 internal trigger 0 remapping(only for GD32F30X_CL devices) 
      \arg        GPIO_PTP_PPS_REMAP: ethernet PTP PPS remapping(only for GD32F30X_CL devices) 
      \arg        GPIO_TIMER8_REMAP: TIMER8 remapping
      \arg        GPIO_TIMER9_REMAP: TIMER9 remapping
      \arg        GPIO_TIMER10_REMAP: TIMER10 remapping
      \arg        GPIO_TIMER12_REMAP: TIMER12 remapping
      \arg        GPIO_TIMER13_REMAP: TIMER13 remapping
      \arg        GPIO_EXMC_NADV_REMAP: EXMC_NADV connect/disconnect
      \arg        GPIO_CTC_REMAP0: CTC remapping(PD15)
      \arg        GPIO_CTC_REMAP1: CTC remapping(PF0)
    \param[in]  newvalue: ENABLE or DISABLE
    \param[out] none
    \retval     none
*/
void gpio_pin_remap_config(uint32_t gpio_remap, ControlStatus newvalue)
{
    uint32_t remap1 = 0U, remap2 = 0U, temp_reg = 0U, temp_mask = 0U;

    if(((uint32_t)0x80000000U) == (gpio_remap & 0x80000000U)){
        /* get AFIO_PCF1 regiter value */
        temp_reg = AFIO_PCF1;
    }else{
        /* get AFIO_PCF0 regiter value */
        temp_reg = AFIO_PCF0;
    }

    temp_mask = (gpio_remap & PCF_POSITION_MASK) >> 0x10U;
    remap1 = gpio_remap & LSB_16BIT_MASK;

    /* judge pin remap type */
    if((PCF_LOCATION1_MASK | PCF_LOCATION2_MASK) == (gpio_remap & (PCF_LOCATION1_MASK | PCF_LOCATION2_MASK))){
        temp_reg &= PCF_SWJCFG_MASK;
        AFIO_PCF0 &= PCF_SWJCFG_MASK;
    }else if(PCF_LOCATION2_MASK == (gpio_remap & PCF_LOCATION2_MASK)){
        remap2 = ((uint32_t)0x03U) << temp_mask;
        temp_reg &= ~remap2;
        temp_reg |= ~PCF_SWJCFG_MASK;
    }else{
        temp_reg &= ~(remap1 << ((gpio_remap >> 0x15U)*0x10U));
        temp_reg |= ~PCF_SWJCFG_MASK;
    }
    
    /* set pin remap value */
    if(DISABLE != newvalue){
        temp_reg |= (remap1 << ((gpio_remap >> 0x15U)*0x10U));
    }
    
    if(AFIO_PCF1_FIELDS == (gpio_remap & AFIO_PCF1_FIELDS)){
        /* set AFIO_PCF1 regiter value */
        AFIO_PCF1 = temp_reg;
    }else{
        /* set AFIO_PCF0 regiter value */
        AFIO_PCF0 = temp_reg;
    }
}

/*!
    \brief      configure the I/O compensation cell
    \param[in]  compensation: specifies the I/O compensation cell mode
      \arg        GPIO_COMPENSATION_ENABLE: I/O compensation cell is enabled
      \arg        GPIO_COMPENSATION_DISABLE: I/O compensation cell is disabled
    \param[out] none
    \retval     none
*/
void gpio_compensation_config(uint32_t compensation)
{
    uint32_t reg;
    reg = AFIO_CPSCTL;

    /* reset the AFIO_CPSCTL_CPS_EN bit and set according to gpio_compensation */
    reg &= ~AFIO_CPSCTL_CPS_EN;
    AFIO_CPSCTL = (reg | compensation);
}

/*!
    \brief      check the I/O compensation cell is ready or not
    \param[in]  none
    \param[out] none
    \retval     FlagStatus: SET or RESET
  */
FlagStatus gpio_compensation_flag_get(void)
{
    if(((uint32_t)RESET) != (AFIO_CPSCTL & AFIO_CPSCTL_CPS_RDY)){
        return SET;
    }else{
        return RESET;
    }
}
