/*****************************************************************************
* © 2014 Microchip Technology Inc. and its subsidiaries.
* You may use this software and any derivatives exclusively with
* Microchip products.
* THIS SOFTWARE IS SUPPLIED BY MICROCHIP "AS IS".
* NO WARRANTIES, WHETHER EXPRESS, IMPLIED OR STATUTORY, APPLY TO THIS SOFTWARE,
* INCLUDING ANY IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY,
* AND FITNESS FOR A PARTICULAR PURPOSE, OR ITS INTERACTION WITH MICROCHIP
* PRODUCTS, COMBINATION WITH ANY OTHER PRODUCTS, OR USE IN ANY APPLICATION.
* IN NO EVENT WILL MICROCHIP BE LIABLE FOR ANY INDIRECT, SPECIAL, PUNITIVE,
* INCIDENTAL OR CONSEQUENTIAL LOSS, DAMAGE, COST OR EXPENSE OF ANY KIND
* WHATSOEVER RELATED TO THE SOFTWARE, HOWEVER CAUSED, EVEN IF MICROCHIP HAS
* BEEN ADVISED OF THE POSSIBILITY OR THE DAMAGES ARE FORESEEABLE.
* TO THE FULLEST EXTENT ALLOWED BY LAW, MICROCHIP'S TOTAL LIABILITY ON ALL
* CLAIMS IN ANY WAY RELATED TO THIS SOFTWARE WILL NOT EXCEED THE AMOUNT OF
* FEES, IF ANY, THAT YOU HAVE PAID DIRECTLY TO MICROCHIP FOR THIS SOFTWARE.
* MICROCHIP PROVIDES THIS SOFTWARE CONDITIONALLY UPON YOUR ACCEPTANCE
* OF THESE TERMS.
*****************************************************************************/


/** @file mec14xx_gpio.c
 *MEC14xx GPIO hardware access
 */
/** @defgroup MEC14xx Peripherals GPIO
 *  @{
 */


#include "appcfg.h"
#include "platform.h"
#include "MEC14xx/mec14xx.h"
#include "MEC14xx/mec14xx_gpio.h"



static uint32_t gpio_has_drv_str ( enum gpio_id_t gpio_id );


#ifdef ENABLE_GPIO_PIN_VALIDATION

static const uint32_t gpio_port_bitmaps[NUM_GPIO_PORTS] = 
{
    (GPIO_PORT_A_BITMAP),
    (GPIO_PORT_B_BITMAP),
    (GPIO_PORT_C_BITMAP),
    (GPIO_PORT_D_BITMAP)
};

#endif

// 
// Drive Strength Register bitmap
//
static const uint32_t gpio_drv_str_bitmap[NUM_GPIO_PORTS] = 
{
    (GPIO_PORT_A_DRVSTR_BITMAP),
    (GPIO_PORT_B_DRVSTR_BITMAP),
    (GPIO_PORT_C_DRVSTR_BITMAP),
    (GPIO_PORT_D_DRVSTR_BITMAP)
};


struct gpio_cfg 
{
   uint16_t bit_mask;
   uint8_t bit_pos;
};

static const struct gpio_cfg gpio_cfg_tbl[GPIO_PROP_MAX] = 
{
   { 0x0003u, 0x00u },
   { 0x000Cu, 0x02u },
   { 0x00F0u, 0x04u },
   { 0x0100u, 0x08u },
   { 0x0200u, 0x09u },
   { 0x0400u, 0x0Au },
   { 0x0800u, 0x0Bu },
   { 0x3000u, 0x0Cu },
   { 0x3FFFu, 0x00u }
};

static uint32_t gpio_pin_ctrl_addr(enum gpio_id_t gpio_id)
{
    return ((uint32_t)(GPIO_BASE) + (uint32_t)(gpio_id << 2));
}

#ifdef ENABLE_GPIO_PIN_VALIDATION

/**
 * gpio_is_valid - local helper checks if GPIO pin is 
 * implemented in this hardware. 
 * 
 * @author sworley 
 * 
 * @param gpio_id 0-based GPIO ID
 * 
 * @return uint8_t Non-zero(GPIO Pin implemented), 0(not 
 *         implemented).
 */
static uint8_t gpio_is_valid ( enum gpio_id_t gpio_id )
{
   uint16_t gp_bank;

   gp_bank = 0;

   if ( (uint16_t)gpio_id < (uint16_t)(MAX_GPIO_ID) )
   {
      gp_bank = (uint16_t)gpio_id >> 5;
      if ( gpio_port_bitmaps[gp_bank] & (1 << (gpio_id & 0x001Fu)) )
      { 
         return true;
      }
   }

   return false;
}

#else
static uint32_t gpio_is_valid(enum gpio_id_t gpio_id) { return true; }

#endif

static uint8_t gpio_bank_num(enum gpio_id_t gpio_id)
{
    return (uint8_t)(gpio_id) >> 5;
}


static uint8_t gpio_pin_num(enum gpio_id_t gpio_id)
{
    return (uint8_t)(gpio_id) & 0x1Fu;
}


/**
 * gpio_has_drv_str - Local helper to check if GPIO pin has 
 * associated drive strength register. 
 * 
 * @author sworley 
 * 
 * @param gpio_id 0-based GPIO ID
 * 
 * @return uint32_t 0(No Drive Strength), Non-zero(Physical 
 *         address of Drive Strength Register).
 */
static uint32_t gpio_has_drv_str ( enum gpio_id_t gpio_id )
{
    uint32_t bank, bitpos, addr;
    
    addr = 0ul;
    if ( gpio_id < MAX_GPIO_ID )
    {
        bank = gpio_bank_num(gpio_id);
        bitpos = gpio_pin_num(gpio_id);
        if ( gpio_drv_str_bitmap[bank] & (1ul << bitpos) )
        {
            addr = (GPIO_PCTRL2_BASE) + ((uint32_t)(gpio_id) << 2);
            if ( gpio_id > GPIO_0077_ID )
            {
                addr -= 0x20ul;
            }
        }
    }
    
    return addr;
}


uint16_t GPIOGetConfig(enum gpio_id_t gpio_id)
{
    if (gpio_is_valid(gpio_id)) {
        return  *((volatile uint16_t *)gpio_pin_ctrl_addr(gpio_id));
    } else {
        return 0u;
    }
}


void GPIOSetConfig(enum gpio_id_t gpio_id, uint16_t config)
{
    volatile uint16_t * p;
    
    if (gpio_is_valid(gpio_id)) {
        p = (volatile uint16_t *)gpio_pin_ctrl_addr(gpio_id);
        *p = config;
    }
}


void GPIOConfigAndOr(enum gpio_id_t gpio_id, uint16_t and_mask, uint16_t or_mask)
{
    volatile uint16_t * p;


    if (gpio_is_valid(gpio_id)) {
        p = (volatile uint16_t *)gpio_pin_ctrl_addr(gpio_id);
        *p = (*p & and_mask) | or_mask;
    }
}


uint32_t GPIOGetControl(enum gpio_id_t gpio_id)
{
    if (gpio_is_valid(gpio_id)) {
        return *((volatile uint32_t *)gpio_pin_ctrl_addr(gpio_id));
    } else {
        return 0xFFFFFFFFul;
    }
}


void GPIOSetControl(enum gpio_id_t gpio_id, uint32_t ctrl_val)
{
    volatile uint32_t * p;
    
    if (gpio_is_valid(gpio_id)) {
        p = (volatile uint32_t *)gpio_pin_ctrl_addr(gpio_id);
        *p = ctrl_val;
    }
}


void GPIOControlAndOr(enum gpio_id_t gpio_id, uint32_t and_mask, uint32_t or_mask)
{
    volatile uint32_t * p;

    if (gpio_is_valid(gpio_id)) {
        p = (volatile uint32_t *)gpio_pin_ctrl_addr(gpio_id);
        *p = (*p & and_mask) | or_mask;
    }
}


/**
 * GPIOPropertySet - Program specified GPIO Pin configuration 
 * item. 
 * 
 * @author sworley 
 * 
 * @param gpio_id 0-based GPIO ID
 * @param gpio_prop enumerated GPIO Property(configuration item)
 * @param prop_val new property value
 */
void GPIOPropertySet ( enum gpio_id_t gpio_id, 
                       enum gpio_prop_t gpio_prop,
                       uint16_t prop_val
                     )
{
    volatile uint16_t * p;
    uint16_t gp_cfg;
  
    gp_cfg = 0u;

    if ( gpio_is_valid(gpio_id) && ((uint16_t)gpio_prop < (uint16_t)GPIO_PROP_MAX) )
    {
        p = (volatile uint16_t *)gpio_pin_ctrl_addr(gpio_id);
        gp_cfg = *p;
        gp_cfg &= ~(gpio_cfg_tbl[gpio_prop].bit_mask);
        gp_cfg |= (prop_val << gpio_cfg_tbl[gpio_prop].bit_pos) & 
                  gpio_cfg_tbl[gpio_prop].bit_mask; 
        *p = gp_cfg;
    }
}


/**
 * GPIOGetSlewRate - Return GPIO Pin Slew Rate
 * 
 * @author sworley 
 * 
 * @param gpio_id 0-based GPIO ID
 * 
 * @return uint8_t GPIO Pin Slew Rate: 0(Slow) or 1(Fast)
 */
uint8_t GPIOGetSlewRate( enum gpio_id_t gpio_id )
{
    uint32_t addr;
    uint8_t slew;
    
    addr = gpio_has_drv_str(gpio_id);
    if ( 0ul != addr )
    {
        slew = ((*(volatile uint8_t *)addr) >> GPIO_DRV_SLEW_BITPOS) & 0x01u;
    }
    else
    {
        slew = 0u;
    }
    
    return slew;
}


/**
 * GPIOSetSlewRate - Program GPIO Pin's Slew Rate
 * 
 * @author sworley 
 * 
 * @param gpio_id 0-based GPIO ID
 * @param slew_rate new slew rate: 0(Slow), Non-zero(Fast)
 */
void GPIOSetSlewRate ( enum gpio_id_t gpio_id,
                       enum gpio_slew_rate_t slew_rate )
{
    uint32_t addr;
    
    addr = gpio_has_drv_str(gpio_id );
    if ( addr )
    {
        *(volatile uint8_t *)addr = (*(volatile uint8_t *)addr & 
            ~(GPIO_DRV_SLEW_MASK)) | 
            ((slew_rate << (GPIO_DRV_SLEW_BITPOS)) & (GPIO_DRV_SLEW_MASK));
    }
}


/**
 * GPIOGetDriveStr - Get GPIO Pin's Drive Strength 
 * 
 * @author sworley 
 * 
 * @param gpio_id 0-based GPIO ID
 * 
 * @return uint8_t Pin Drive Strength: 0=2mA, 1=4mA, 2=8mA, 
 *         3=12mA.
 */
uint8_t GPIOGetDriveStr ( enum gpio_id_t gpio_id )
{
    uint32_t addr;
    
    addr = gpio_has_drv_str(gpio_id );
    if ( addr )
    {
        return ((*(volatile uint8_t *)addr) >> GPIO_DRV_STR_BITPOS) & (GPIO_DRV_STR_MASK);
    }
    else
    {
        return 0u;
    }
}


/**
 * GPIOSetDriveStr - Program GPIO Pin's Drive Strength
 * 
 * @author sworley 
 * 
 * @param gpio_id 0-based GPIO ID
 * @param drv_str enumerated drive strength: 0=2mA, 1=4mA, 
 *                2=8mA, 3=12mA
 */
void GPIOSetDriveStr ( enum gpio_id_t gpio_id,
                       enum gpio_drv_str_t drv_str )
{
    uint32_t addr;
    uint8_t r8;
    
    addr = gpio_has_drv_str(gpio_id);
    if ( addr )
    {
        r8 = *(volatile uint8_t *)addr & ~(GPIO_DRV_STR_MASK);
        r8 += ((drv_str << GPIO_DRV_STR_BITPOS) & GPIO_DRV_STR_MASK);
        *(volatile uint8_t *)addr = r8;
    }
}


/**
 * GPIOGetDriveStrAndSlew - Return combined value representing 
 * Drive Strength and Slew Rate. 
 * 
 * @author sworley 
 * 
 * @param gpio_id 0-based GPIO ID
 * 
 * @return uint8_t bit[0] = Slew Rate, bits[3:1]=0(Reserved), 
 *         bits[5:4]=Drive Strength, bits[7:6]=0(Reserved)
 */
uint8_t GPIOGetDriveStrAndSlew ( enum gpio_id_t gpio_id )
{
    uint32_t addr;
    
    addr = gpio_has_drv_str(gpio_id );
    if ( addr )
    {
        return (*(volatile uint8_t *)addr);
    }
    else
    {
        return 0u;
    }
}


/**
 * GPIOSetDriveStrAndSlew - Program GPIO Pin's drive strength 
 * and slew rate. 
 * 
 * @author sworley 
 * 
 * @param gpio_id 0-based GPIO ID
 * @param drv_and_slew bit[0] = Slew Rate, bits[3:1]=0(Reserved), 
 *         bits[5:4]=Drive Strength, bits[7:6]=0(Reserved)
 */
void GPIOSetDriveStrAndSlew ( enum gpio_id_t gpio_id,
                              uint8_t drv_and_slew )
{
    uint32_t addr;
    uint8_t r8;
    
    addr = gpio_has_drv_str(gpio_id);
    if ( addr )
    {
        r8 = *(volatile uint8_t *)addr & ~(GPIO_DRV_SLEW_MASK + GPIO_DRV_STR_MASK);
        r8 |= (drv_and_slew & (GPIO_DRV_SLEW_MASK + GPIO_DRV_STR_MASK));
        *(volatile uint8_t *)addr = r8;
    }
}


/**
 * GPIOSetOutput - Program GPIO Pin's output state using Pin 
 * configuration register (not parallel output register). 
 * 
 * @author sworley 
 * 
 * @param gpio_id 0-based GPIO ID
 * @param gpio_state pin state: actual pin state at pad will 
 *                   depend upon GPIO Output invert
 *                   configuration.
 * @note peforms a byte wide write to byte offset 2 of the GPIO 
 *       Pin's 32-bit configuration register. No
 *       read-modify-write.
 */
void GPIOSetOutput ( enum gpio_id_t gpio_id, 
                     uint8_t gpio_state
                     )
{
    volatile uint8_t * p;
    
    if ( gpio_is_valid(gpio_id) )
    {
        p = (volatile uint8_t *)(gpio_pin_ctrl_addr(gpio_id) + 2ul);
        if (gpio_state) {
            *p = 0x01u;
        } else {
            *p = 0u;
        }
    }
}


void GPIOToggleOutput ( enum gpio_id_t gpio_id )
{
    volatile uint8_t * p;

    if ( gpio_is_valid(gpio_id) )
    {
        p = (volatile uint8_t *)(gpio_pin_ctrl_addr(gpio_id) + 2ul);
        *p ^= 0x01u;
    }
}


/**
 * GPIOReadPin - Read GPIO Pin's Pad Input from configuration 
 * register. 
 * 
 * @author sworley 
 * 
 * @param gpio_id 0-based GPIO ID.
 * 
 * @return uint8_t 0 or 1 depending upon the state of the GPIO 
 *         pad.
 * @note performs a byte read of offset 3 of the GPIO Pin's 
 *       32-bit configuration register.
 */
uint8_t GPIOReadPin( enum gpio_id_t gpio_id )
{
    if ( gpio_is_valid(gpio_id) )
    {
        return *((volatile uint8_t *)(gpio_pin_ctrl_addr(gpio_id) + 3ul));
    } 
    else 
    {
        return 0u;
    }
}


/** GPIOPinLock - Lock specified GPIO's control register.
 *  @param enum gpio_id_t zero based GPIO ID
 *  @note Lock bit is only cleared on POR. Lock registers
 *  are in reverse order, first register is at top address.
 *  GPIO_LOCK_BASE defined to top(first) register address.
 *  */ 
void GPIOPinLock(enum gpio_id_t gpio_id)
{
    uint32_t addr;
    uint8_t bank, bitpos;

    if (gpio_is_valid(gpio_id)) {
        bank = gpio_bank_num(gpio_id);  // 0 - 4
        bitpos = gpio_pin_num(gpio_id); // 0 - 31
        addr = (uint32_t)(GPIO_LOCK_BASE) - (bank << 2);
        *(volatile uint32_t *)addr |= (1ul << bitpos);
    } 
}


/* end mec14xx_gpio.c */
/**   @}
 */

