/*
 * Copyright (C) 2019 Gunar Schorcht
 *
 * This file is subject to the terms and conditions of the GNU Lesser
 * General Public License v2.1. See the file LICENSE in the top level
 * directory for more details.
 */

/**
 * @ingroup     cpu_esp8266
 * @ingroup     drivers_periph_gpio
 * @{
 *
 * @file
 * @brief       Low-level GPIO driver implementation for ESP8266
 *
 * @author      Gunar Schorcht <gunar@schorcht.net>
 * @}
 */

#define ENABLE_DEBUG    (0)
#include "debug.h"

#include <stdbool.h>

#include "log.h"
#include "periph/gpio.h"    /* RIOT gpio.h */

#include "esp8266/eagle_soc.h"
#include "esp8266/gpio_register.h"
#include "rom/ets_sys.h"

#include "sdk/ets.h"
#include "esp/gpio_regs.h"
#include "esp/iomux_regs.h"
#include "esp/rtc_regs.h"

#include "esp_common.h"
#include "gpio_arch.h"
#include "irq_arch.h"
#include "syscalls.h"

/*
 * IOMUX to GPIO mapping
 * source https://www.espressif.com/sites/default/files/documentation/0d-esp8266_pin_list_release_15-11-2014.xlsx
 */

const uint8_t _gpio_to_iomux[]   = { 12, 5, 13, 4, 14, 15, 6, 7, 8, 9, 10, 11, 0, 1, 2, 3 };
const uint8_t _iomux_to_gpio[]   = { 12, 13, 14, 15, 3, 1, 6, 7, 8, 9, 10, 11, 0, 2, 4, 5 };

gpio_pin_usage_t _gpio_pin_usage [GPIO_PIN_NUMOF] =
{
   _GPIO,   /* gpio0 */

   _UART,   /* gpio1  UART0 RxD */
   _GPIO,   /* gpio2 */
   _UART,   /* gpio3  UART0 TxD */
   _GPIO,   /* gpio4 */
   _GPIO,   /* gpio5 */
   _SPIF,   /* gpio6  SPI flash CLK */
   _SPIF,   /* gpio7  SPI flash MISO */
   _SPIF,   /* gpio8  SPI flash MOSI */
   #if defined(FLASH_MODE_QIO) || defined(FLASH_MODE_QOUT)
   _SPIF,   /* gpio9  SPI flash HD (qio/qout flash mode) */
   _SPIF,   /* gpio10 SPI flash WP (qio/qout flash mode) */
   #else
   _GPIO,   /* gpio9  can be used as GPIO if not needed for flash */
   _GPIO,   /* gpio10 can be used as GPIO if not needed for flash */
   #endif
   _SPIF,   /* gpio11 SPI flash CS */
   _GPIO,   /* gpio12 */
   _GPIO,   /* gpio13 */
   _GPIO,   /* gpio14 */
   _GPIO,   /* gpio15 */
   _GPIO    /* gpio16 */
};

/* String representation of usage types */
const char* _gpio_pin_usage_str[] =
{
    "GPIO", "I2C", "PWM", "SPI", "SPI Flash", "UART", "N/A"
};

int gpio_init(gpio_t pin, gpio_mode_t mode)
{
    DEBUG("%s: %d %d\n", __func__, pin, mode);

    CHECK_PARAM_RET(pin < GPIO_PIN_NUMOF, -1);

    /* check whether pin can be used as GPIO or is used in anyway else */
    switch (_gpio_pin_usage[pin]) {
        case _I2C:  LOG_ERROR("GPIO%d is used as I2C signal.\n", pin); return -1;
        case _PWM:  LOG_ERROR("GPIO%d is used as PWM output.\n", pin); return -1;
        case _SPI:  LOG_ERROR("GPIO%d is used as SPI interface.\n", pin); return -1;
        case _SPIF: LOG_ERROR("GPIO%d is used as SPI flash.\n", pin); return -1;
        case _UART: LOG_ERROR("GPIO%d is used as UART interface.\n", pin); return -1;
        default: break;
    }

    /* GPIO16 requires separate handling */
    if (pin == GPIO16) {
        RTC.GPIO_CFG[3] = (RTC.GPIO_CFG[3] & 0xffffffbc) | BIT(0);
        RTC.GPIO_CONF = RTC.GPIO_CONF & ~RTC_GPIO_CONF_OUT_ENABLE;

        switch (mode) {
            case GPIO_OUT:
                RTC.GPIO_ENABLE = RTC.GPIO_OUT | RTC_GPIO_CONF_OUT_ENABLE;
                break;

            case GPIO_OD:
                LOG_ERROR("GPIO mode GPIO_OD is not supported for GPIO16.\n");
                return -1;

            case GPIO_OD_PU:
                LOG_ERROR("GPIO mode GPIO_OD_PU is not supported for GPIO16.\n");
                return -1;

            case GPIO_IN:
                RTC.GPIO_ENABLE = RTC.GPIO_OUT & ~RTC_GPIO_CONF_OUT_ENABLE;
                RTC.GPIO_CFG[3] &= ~RTC_GPIO_CFG3_PIN_PULLUP;
                break;

            case GPIO_IN_PU:
                LOG_ERROR("GPIO mode GPIO_IN_PU is not supported for GPIO16.\n");
                return -1;

            case GPIO_IN_PD:
                LOG_ERROR("GPIO mode GPIO_IN_PD is not supported for GPIO16.\n");
                return -1;

            default:
                LOG_ERROR("Invalid GPIO mode for GPIO%d.\n", pin);
                return -1;
        }
        return 0;
    }

    uint8_t  iomux      = _gpio_to_iomux [pin];
    uint32_t iomux_conf = (iomux > 11) ? IOMUX_FUNC(0) : IOMUX_FUNC(3);

    switch (mode) {

        case GPIO_OUT:   iomux_conf |= IOMUX_PIN_OUTPUT_ENABLE;
                         iomux_conf |= IOMUX_PIN_OUTPUT_ENABLE_SLEEP;
                         GPIO.CONF[pin] &= ~GPIO_CONF_OPEN_DRAIN;
                         GPIO.ENABLE_OUT_SET = BIT(pin);
                         break;

        case GPIO_OD_PU: iomux_conf |= IOMUX_PIN_PULLUP;
                         iomux_conf |= IOMUX_PIN_PULLUP_SLEEP;
        case GPIO_OD:    iomux_conf |= IOMUX_PIN_OUTPUT_ENABLE;
                         iomux_conf |= IOMUX_PIN_OUTPUT_ENABLE_SLEEP;
                         GPIO.CONF[pin] |= GPIO_CONF_OPEN_DRAIN;
                         GPIO.ENABLE_OUT_SET = BIT(pin);
                         break;

        case GPIO_IN_PU: iomux_conf |= IOMUX_PIN_PULLUP;
                         iomux_conf |= IOMUX_PIN_PULLUP_SLEEP;
        case GPIO_IN:    GPIO.CONF[pin] |= GPIO_CONF_OPEN_DRAIN;
                         GPIO.ENABLE_OUT_CLEAR = BIT(pin);
                         break;

        case GPIO_IN_PD: LOG_ERROR("GPIO mode GPIO_IN_PD is not supported.\n");
                         return -1;

        default: LOG_ERROR("Invalid GPIO mode for GPIO%d.\n", pin);
    }

    IOMUX.PIN[iomux] = iomux_conf;

    return 0;
}

#ifdef MODULE_PERIPH_GPIO_IRQ
static gpio_isr_ctx_t gpio_isr_ctx_table [GPIO_PIN_NUMOF] = { };
static bool gpio_int_enabled_table [GPIO_PIN_NUMOF] = { };

void IRAM gpio_int_handler (void* arg)
{
    irq_isr_enter();

    for (int i = 0; i < GPIO_PIN_NUMOF; i++) {

        uint32_t mask = BIT(i);

        if (GPIO.STATUS & mask) {
            GPIO.STATUS_CLEAR = mask;
            if (gpio_int_enabled_table[i] && (GPIO.CONF[i] & GPIO_PIN_INT_TYPE_MASK)) {
                gpio_isr_ctx_table[i].cb (gpio_isr_ctx_table[i].arg);
            }
        }
        mask = mask << 1;
    }

    irq_isr_exit();
}

int gpio_init_int(gpio_t pin, gpio_mode_t mode, gpio_flank_t flank,
                  gpio_cb_t cb, void *arg)
{
    if (gpio_init(pin, mode)) {
        return -1;
    }

    if (pin == GPIO16) {
        /* GPIO16 requires separate handling */
        LOG_ERROR("GPIO16 cannot generate interrupts.\n");
        return -1;
    }
    gpio_isr_ctx_table[pin].cb  = cb;
    gpio_isr_ctx_table[pin].arg = arg;

    GPIO.CONF[pin] = SET_FIELD(GPIO.CONF[pin], GPIO_CONF_INTTYPE, flank);
    if (flank != GPIO_NONE) {
        gpio_int_enabled_table [pin] = (gpio_isr_ctx_table[pin].cb != NULL);
        ets_isr_attach (ETS_GPIO_INUM, gpio_int_handler, 0);
        ets_isr_unmask ((1 << ETS_GPIO_INUM));
    }

    return 0;
}

void gpio_irq_enable (gpio_t pin)
{
    CHECK_PARAM(pin < GPIO_PIN_NUMOF);

    gpio_int_enabled_table [pin] = true;
}

void gpio_irq_disable (gpio_t pin)
{
    CHECK_PARAM(pin < GPIO_PIN_NUMOF);

    gpio_int_enabled_table [pin] = false;
}
#endif /* MODULE_PERIPH_GPIO_IRQ */

int gpio_read (gpio_t pin)
{
    CHECK_PARAM_RET(pin < GPIO_PIN_NUMOF, -1);

    if (pin == GPIO16) {
        /* GPIO16 requires separate handling */
        return RTC.GPIO_IN & BIT(0);
    }

    return (GPIO.IN & BIT(pin)) ? 1 : 0;
}

void gpio_write (gpio_t pin, int value)
{
    DEBUG("%s: %d %d\n", __func__, pin, value);

    CHECK_PARAM(pin < GPIO_PIN_NUMOF);

    if (pin == GPIO16) {
        /* GPIO16 requires separate handling */
        RTC.GPIO_OUT = (RTC.GPIO_OUT & ~BIT(0)) | (value ? 1 : 0);
        return;
    }

    if (value) {
        GPIO.OUT_SET = BIT(pin) & GPIO_OUT_PIN_MASK;
    }
    else {
        GPIO.OUT_CLEAR = BIT(pin) & GPIO_OUT_PIN_MASK;
    }
}

void gpio_set (gpio_t pin)
{
    gpio_write (pin, 1);
}

void gpio_clear (gpio_t pin)
{
    gpio_write (pin, 0);
}

void gpio_toggle (gpio_t pin)
{
    DEBUG("%s: %d\n", __func__, pin);

    CHECK_PARAM(pin < GPIO_PIN_NUMOF);

    if (pin == GPIO16) {
        /* GPIO16 requires separate handling */
        RTC.GPIO_OUT = (RTC.GPIO_OUT & ~BIT(0)) | ((RTC.GPIO_IN & BIT(0)) ? 0 : 1);
        return;
    }

    GPIO.OUT ^= BIT(pin);
}

int gpio_set_pin_usage(gpio_t pin, gpio_pin_usage_t usage)
{
    CHECK_PARAM_RET(pin < GPIO_PIN_NUMOF, -1);
    _gpio_pin_usage [pin] = usage;
    return 0;
}

gpio_pin_usage_t gpio_get_pin_usage (gpio_t pin)
{
    return (pin < GPIO_PIN_NUMOF) ? _gpio_pin_usage[pin] : _NOT_EXIST;
}

const char* gpio_get_pin_usage_str(gpio_t pin)
{
    return _gpio_pin_usage_str[_gpio_pin_usage[((pin < GPIO_PIN_NUMOF) ? pin : _NOT_EXIST)]];
}
