/**
 * \addtogroup BSP
 * \{
 * \addtogroup DEVICES
 * \{
 * \addtogroup GPIO
 * \{
 */

/**
 ****************************************************************************************
 *
 * @file hw_gpio.c
 *
 * @brief Implementation of the GPIO Low Level Driver.
 *
 * Copyright (c) 2016, Dialog Semiconductor
 * All rights reserved.
 * 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.
 *
 ****************************************************************************************
 */

#if dg_configUSE_HW_GPIO


#include <stdint.h>
#include <hw_gpio.h>

/* Register adresses */
#define PX_DATA_REG_ADDR(_port)         ((uint16_t *)(GPIO_BASE + offsetof(GPIO_Type, P0_DATA_REG)) + _port)
#define PX_DATA_REG(_port)              *PX_DATA_REG_ADDR(_port)
#define PX_SET_DATA_REG_ADDR(_port)     ((uint16_t *)(GPIO_BASE + offsetof(GPIO_Type, P0_SET_DATA_REG)) + _port)
#define PX_SET_DATA_REG(_port)          *PX_SET_DATA_REG_ADDR(_port)
#define PX_RESET_DATA_REG_ADDR(_port)   ((uint16_t *)(GPIO_BASE + offsetof(GPIO_Type, P0_RESET_DATA_REG)) + _port)
#define PX_RESET_DATA_REG(_port)        *PX_RESET_DATA_REG_ADDR(_port)
#define PXX_MODE_REG_ADDR(_port, _pin)  ((uint16_t *)(GPIO_BASE + offsetof(GPIO_Type, P00_MODE_REG)) + (_port * 8)  + _pin)
#define PXX_MODE_REG(_port, _pin)       *PXX_MODE_REG_ADDR(_port, _pin)
#define PX_PADPWR_CTRL_REG_ADDR(_port)  ((uint16_t *)(GPIO_BASE + offsetof(GPIO_Type, P0_PADPWR_CTRL_REG)) + _port)
#define PX_PADPWR_CTRL_REG(_port)       *PX_PADPWR_CTRL_REG_ADDR(_port)

/* on FPGA we cannot read the state of an output GPIO */

#ifdef FPGA_PAD_LOOPBACK_BROKEN
static uint16_t Px_DATA_REG_cache[5];
#endif

#if (dg_configIMAGE_SETUP == PRODUCTION_MODE) && (DEBUG_GPIO_ALLOC_MONITOR_ENABLED == 1)
#warning "GPIO assignment monitoring is active in PRODUCTION build!"
#endif

static volatile uint8_t GPIO_status[HW_GPIO_NUM_PORTS] = { 0 };

const uint8_t hw_gpio_port_num_pins[HW_GPIO_NUM_PORTS] =
{
    HW_GPIO_PORT_0_NUM_PINS, HW_GPIO_PORT_1_NUM_PINS,
    HW_GPIO_PORT_2_NUM_PINS, HW_GPIO_PORT_3_NUM_PINS,
    HW_GPIO_PORT_4_NUM_PINS
};

/*
 * Global Functions
 ****************************************************************************************
 */

void hw_gpio_configure(const gpio_config cfg[])
{
#if dg_configIMAGE_SETUP == DEVELOPMENT_MODE
    int num_pins = 0;
    uint8_t set_mask[HW_GPIO_NUM_PORTS]; // = { };
#endif

    if (!cfg)
    {
        return;
    }

    while (cfg->pin != 0xFF)
    {
        uint8_t port = cfg->pin >> 4;
        uint8_t pin = cfg->pin & 0x0F;

#if dg_configIMAGE_SETUP == DEVELOPMENT_MODE

        if (port >= HW_GPIO_NUM_PORTS || pin >= hw_gpio_port_num_pins[port])
        {
            /*
             * invalid port or pin number specified, it was either incorrectly specified
             * of cfg was not terminated properly using HW_GPIO_PINCONFIG_END so we're
             * reading garbage
             */
            __BKPT(0);
        }

        if (++num_pins > HW_GPIO_NUM_PINS)
        {
            /*
             * trying to set more pins than available, perhaps cfg was not terminated
             * properly using HW_GPIO_PINCONFIG_END?
             */
            __BKPT(0);
        }

        if (set_mask[port] & (1 << pin))
        {
            /*
             * trying to set pin which has been already set by this call which means
             * there is duplicated pin in configuration - does not make sense
             */
            __BKPT(0);
        }

        set_mask[port] |= (1 << pin);
#endif

        if (cfg->reserve)
        {
            hw_gpio_reserve_and_configure_pin(port, pin, cfg->mode, cfg->func, cfg->high);
        }
        else
        {
            hw_gpio_configure_pin(port, pin, cfg->mode, cfg->func, cfg->high);
        }

        cfg++;
    }
}

bool hw_gpio_reserve_pin(HW_GPIO_PORT port, HW_GPIO_PIN pin)
{
    if ((GPIO_status[port] & (1 << pin)))
    {
        return false;
    }

    GPIO_status[port] |= (1 << pin);

    return true;
}

bool hw_gpio_reserve_and_configure_pin(HW_GPIO_PORT port, HW_GPIO_PIN pin, HW_GPIO_MODE mode,
                                       HW_GPIO_FUNC function, bool high)
{
    if (!hw_gpio_reserve_pin(port, pin))
    {
        return false;
    }

    hw_gpio_configure_pin(port, pin, mode, function, high);

    return true;
}

void hw_gpio_unreserve_pin(HW_GPIO_PORT port, HW_GPIO_PIN pin)
{
    GPIO_status[port] &= ~(1 << pin);
}

static void hw_gpio_verify_reserved(HW_GPIO_PORT port, HW_GPIO_PIN pin)
{
#if (DEBUG_GPIO_ALLOC_MONITOR_ENABLED == 1)

    if (!(GPIO_status[port] & (1 << pin)))
    {
        // If debugger stops at this line, there is configuration problem
        // pin is used without being reserved first
        __BKPT(0); /* this pin has not been previously reserved! */
    }

#endif // (DEBUG_GPIO_ALLOC_MONITOR_ENABLED == 1)
}


void hw_gpio_set_pin_function(HW_GPIO_PORT port, HW_GPIO_PIN pin, HW_GPIO_MODE mode,
                              HW_GPIO_FUNC function)
{
    hw_gpio_verify_reserved(port, pin);

    PXX_MODE_REG(port, pin) = mode | function;
}

void hw_gpio_get_pin_function(HW_GPIO_PORT port, HW_GPIO_PIN pin, HW_GPIO_MODE *mode,
                              HW_GPIO_FUNC *function)
{
    uint16_t val;

    hw_gpio_verify_reserved(port, pin);

    val = PXX_MODE_REG(port, pin);
    *mode = val & 0x0700;
    *function = val & 0x00ff;
}

void hw_gpio_configure_pin(HW_GPIO_PORT port, HW_GPIO_PIN pin, HW_GPIO_MODE mode,
                           HW_GPIO_FUNC function, const bool high)
{
    hw_gpio_verify_reserved(port, pin);

    if (high)
    {
        hw_gpio_set_active(port, pin);
    }
    else
    {
        hw_gpio_set_inactive(port, pin);
    }

    hw_gpio_set_pin_function(port, pin, mode, function);
}

void hw_gpio_configure_pin_power(HW_GPIO_PORT port, HW_GPIO_PIN pin, HW_GPIO_POWER power)
{
    uint16_t padpwr;

    padpwr = PX_PADPWR_CTRL_REG(port);
    padpwr &= ~(1 << pin);

    if (power == HW_GPIO_POWER_VDD1V8P)
    {
        padpwr |= (1 << pin);
    }

    PX_PADPWR_CTRL_REG(port) = padpwr;
}

void hw_gpio_set_active(HW_GPIO_PORT port, HW_GPIO_PIN pin)
{
    hw_gpio_verify_reserved(port, pin);

    PX_SET_DATA_REG(port) = 1 << pin;
#ifdef FPGA_PAD_LOOPBACK_BROKEN
    Px_DATA_REG_cache[port] |= 1 << pin;
#endif
}

void hw_gpio_set_inactive(HW_GPIO_PORT port, HW_GPIO_PIN pin)
{
    hw_gpio_verify_reserved(port, pin);

    PX_RESET_DATA_REG(port) = 1 << pin;
#ifdef FPGA_PAD_LOOPBACK_BROKEN
    Px_DATA_REG_cache[port] &= ~(1 << pin);
#endif
}

bool hw_gpio_get_pin_status(HW_GPIO_PORT port, HW_GPIO_PIN pin)
{
#ifdef FPGA_PAD_LOOPBACK_BROKEN
    HW_GPIO_MODE mode;
    HW_GPIO_FUNC function;
#endif
    hw_gpio_verify_reserved(port, pin);

#ifdef FPGA_PAD_LOOPBACK_BROKEN
    hw_gpio_get_pin_function(port, pin, &mode, &function);

    if (mode != HW_GPIO_MODE_OUTPUT)
    {
        return !!(PX_DATA_REG(port) & (1 << pin));
    }
    else
    {
        return !!(Px_DATA_REG_cache[port] & (1 << pin));
    }

#else
    return ((PX_DATA_REG(port) & (1 << pin)) != 0);
#endif
}

void hw_gpio_toggle(HW_GPIO_PORT port, HW_GPIO_PIN pin)
{
    hw_gpio_verify_reserved(port, pin);

    if (hw_gpio_get_pin_status(port, pin))
    {
        hw_gpio_set_inactive(port, pin);
    }
    else
    {
        hw_gpio_set_active(port, pin);
    }
}

int hw_gpio_get_pins_with_function(HW_GPIO_FUNC func, uint8_t *buf, int buf_size)
{
    int count = 0;
    int port;
    int pin;
    HW_GPIO_MODE mode;
    HW_GPIO_FUNC pin_func;


    for (port = 0; port < HW_GPIO_NUM_PORTS; ++port)
    {
        for (pin = 0; pin < hw_gpio_port_num_pins[port]; ++pin)
        {
            hw_gpio_get_pin_function(port, pin, &mode, &pin_func);

            if (pin_func != func)
            {
                continue;
            }

            if (count < buf_size && buf != NULL)
            {
                buf[count] = (uint8_t)((port << 4) | pin);
            }

            count++;
        }
    }

    return count;
}

#endif /* dg_configUSE_HW_GPIO */
/**
 * \}
 * \}
 * \}
 */
