/*
 * Copyright (C) 2020-2021 Aloysius Indrayanto
 *
 * This file is part of the eCxx library, see LICENSE file for the license details.
 */

/*
 * ----------------------------------------------------------------------------------
 * The code in this file is copied (with a few modifications) from:
 *     core_esp8266_wiring_digital.cpp - wiring digital implementation for esp8266
 * ----------------------------------------------------------------------------------
 * Copyright (C) 2015 Hristo Gochkov. All rights reserved.
 *
 * This file is part of the esp8266 core for Arduino environment.
 *
 * This library is free software; you can redistribute it and/or modify it under the
 * terms of the GNU Lesser General Public License as published by the Free Software
 * Foundation; either version 2.1 of the License, or (at your option) any later
 * version.
 *
 * This library is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
 * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License along
 * with this library; if not, write to the Free Software Foundation, Inc., 51
 * Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
 * ----------------------------------------------------------------------------------
 */


#include "CoreLib.h"


namespace eCxx {

namespace NodeMCU {


//
// Set pin mode
//
extern void ICACHE_RAM_ATTR pinMode(uint8_t pin, uint8_t mode)
{
    // Check the pin number
    if( !userGPIOPin<false>(pin) ) return;

    // Stop waveform output
    stopWaveform(pin);

    // Set pin mode for GPIO16
    if(pin == 16) {
        GPF16 = GP16FFS( GPFFS_GPIO(16) ); // Set mode to GPIO
        GPC16 = 0;
        if(mode == INPUT || mode == INPUT_PULLDOWN_16) {
            if(mode == INPUT_PULLDOWN_16) GPF16 |= _BV(GP16FPD); // Enable Pulldown
            GP16E &= ~1;
        }
        else if(mode == OUTPUT) {
            GP16E |= 1;
        }
        return;
    }

    // Set pin mode for GPIO[0:15]
    if(mode == SPECIAL) {
        _GPF(pin) = GPFFS( GPFFS_BUS(pin) );        // Set mode to BUS (RX0, TX0, TX1, SPI, HSPI or CLK depending in the pin)
        _GPC(pin) = ( _GPC(pin) & (0x0F << GPCI) ); // SOURCE(GPIO) | DRIVER(NORMAL) | INT_TYPE(UNCHANGED) | WAKEUP_ENABLE(DISABLED)
        GPEC      = _BV(pin);                       // Disable data
        if(pin == 3) _GPF(pin) |= _BV(GPFPU);       // Enable pullup on Rx
    }
    else if(mode & FUNCTION_0) {
        _GPF(pin) = GPFFS( (mode >> 4) & 0x07 );
        _GPC(pin) = ( _GPC(pin) & (0x0F << GPCI) );                  // SOURCE(GPIO) | DRIVER(NORMAL) | INT_TYPE(UNCHANGED) | WAKEUP_ENABLE(DISABLED)
        GPEC      = _BV(pin);                                        // Disable data
        if(pin == 13 && mode == FUNCTION_4) _GPF(pin) |= _BV(GPFPU); // Enable pullup on Rx
    }
    else if(mode == OUTPUT || mode == OUTPUT_OPEN_DRAIN) {
        _GPF(pin) = GPFFS( GPFFS_GPIO(pin) );                 // Set mode to GPIO
        _GPC(pin) = ( _GPC(pin) & (0x0F << GPCI) );           // SOURCE(GPIO) | DRIVER(NORMAL) | INT_TYPE(UNCHANGED) | WAKEUP_ENABLE(DISABLED)
        GPES      = _BV(pin);                                 // Enable data
        if(mode == OUTPUT_OPEN_DRAIN) _GPC(pin) |= _BV(GPCD); // Enable open drain
    }
    else if(mode == INPUT || mode == INPUT_PULLUP) {
        _GPF(pin) = GPFFS( GPFFS_GPIO(pin) );                    // Set mode to GPIO
        _GPC(pin) = ( _GPC(pin) & (0x0F << GPCI) ) | _BV(GPCD ); // SOURCE(GPIO) | DRIVER(OPEN_DRAIN) | INT_TYPE(UNCHANGED) | WAKEUP_ENABLE(DISABLED)
        GPEC      = _BV(pin);                                    // Disable data
        if(mode == INPUT_PULLUP)_GPF(pin) |= _BV(GPFPU);         // Enable pullup
    }
    else if(mode == WAKEUP_PULLUP || mode == WAKEUP_PULLDOWN) {
        _GPF(pin) = GPFFS( GPFFS_GPIO(pin) ); // Set mode to GPIO
        GPEC      = _BV(pin);                 // Disable data
        if(mode == WAKEUP_PULLUP) {
            _GPC(pin)  = _BV(GPCD) | (4 << GPCI) | _BV(GPCWE); // SOURCE(GPIO) | DRIVER(OPEN_DRAIN) | INT_TYPE(LOW) | WAKEUP_ENABLE(ENABLED)
            _GPF(pin) |= _BV(GPFPU);                           // Enable pullup
        }
        else {
            _GPC(pin)  = _BV(GPCD) | (5 << GPCI) | _BV(GPCWE); // SOURCE(GPIO) | DRIVER(OPEN_DRAIN) | INT_TYPE(HIGH) | WAKEUP_ENABLE(ENABLED)
            _GPF(pin) |= _BV(GPFPD);                           // Enable pulldown
        }
    }
}


//
// Digital I/O
//
extern void ICACHE_RAM_ATTR digitalWrite(uint8_t pin, bool bit)
{
    // Check the pin number
    if( !userGPIOPin<false>(pin) ) return;

    // GPIO16
    if(pin == 16) {
        if(bit) GP16O |=  static_cast<uint32_t>(1);
        else    GP16O &= ~static_cast<uint32_t>(1);
    }

    // GPIO[0:15]
    if(bit) GPOS = _BV(pin);
    else    GPOC = _BV(pin);
}


extern bool ICACHE_RAM_ATTR digitalRead(uint8_t pin)
{
    // Check the pin number
    if( !userGPIOPin<false>(pin) ) return false;

    // GPIO16
    if(pin == 16) return GP16I & static_cast<uint32_t>(1);

    // GPIO[0:15]
    return GPIP(pin);
}


} // namespace NodeMCU

} // namespace eCxx
