/* Arduino DigitalIO Library
 * Copyright (C) 2013 by William Greiman
 *
 * This file is part of the Arduino DigitalIO Library
 *
 * This Library is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with the Arduino DigitalIO Library.  If not, see
 * <http://www.gnu.org/licenses/>.
 */
/**
 * @file
 * @brief Fast Digital Pin functions
 *
 * @defgroup digitalPin Fast Pin I/O
 * @details  Fast Digital I/O functions and template class.
 * @{
 */
#ifndef DigitalPin_h
#define DigitalPin_h
#if defined(__AVR__) || defined(DOXYGEN)
#include <avr/io.h>
/** GpioPinMap type */
struct GpioPinMap_t
{
    volatile uint8_t *pin;  /**< address of PIN for this pin */
    volatile uint8_t *ddr;  /**< address of DDR for this pin */
    volatile uint8_t *port; /**< address of PORT for this pin */
    uint8_t mask;           /**< bit mask for this pin */
};

/** Initializer macro. */
#define GPIO_PIN(reg, bit)                                                                                             \
    {                                                                                                                  \
        &PIN##reg, &DDR##reg, &PORT##reg, 1 << bit                                                                     \
    }

// Include pin map for current board.
#include "boards/GpioPinMap.h"
//------------------------------------------------------------------------------
/** generate bad pin number error */
void badPinNumber(void) __attribute__((error("Pin number is too large or not a constant")));
//------------------------------------------------------------------------------
/** Check for valid pin number
 * @param[in] pin Number of pin to be checked.
 */
static inline __attribute__((always_inline)) void badPinCheck(uint8_t pin)
{
    if (!__builtin_constant_p(pin) || pin >= NUM_DIGITAL_PINS)
    {
        badPinNumber();
    }
}
//------------------------------------------------------------------------------
/** DDR register address
 * @param[in] pin Arduino pin number
 * @return register address
 */
static inline __attribute__((always_inline)) volatile uint8_t *ddrReg(uint8_t pin)
{
    badPinCheck(pin);
    return GpioPinMap[pin].ddr;
}
//------------------------------------------------------------------------------
/** Bit mask for pin
 * @param[in] pin Arduino pin number
 * @return mask
 */
static inline __attribute__((always_inline)) uint8_t pinMask(uint8_t pin)
{
    badPinCheck(pin);
    return GpioPinMap[pin].mask;
}
//------------------------------------------------------------------------------
/** PIN register address
 * @param[in] pin Arduino pin number
 * @return register address
 */
static inline __attribute__((always_inline)) volatile uint8_t *pinReg(uint8_t pin)
{
    badPinCheck(pin);
    return GpioPinMap[pin].pin;
}
//------------------------------------------------------------------------------
/** PORT register address
 * @param[in] pin Arduino pin number
 * @return register address
 */
static inline __attribute__((always_inline)) volatile uint8_t *portReg(uint8_t pin)
{
    badPinCheck(pin);
    return GpioPinMap[pin].port;
}
//------------------------------------------------------------------------------
/** Fast write helper.
 * @param[in] address I/O register address
 * @param[in] mask bit mask for pin
 * @param[in] level value for bit
 */
static inline __attribute__((always_inline)) void fastBitWriteSafe(volatile uint8_t *address, uint8_t mask, bool level)
{
    uint8_t s;
    if (address > reinterpret_cast<uint8_t *>(0X3F))
    {
        s = SREG;
        cli();
    }
    if (level)
    {
        *address |= mask;
    }
    else
    {
        *address &= ~mask;
    }
    if (address > reinterpret_cast<uint8_t *>(0X3F))
    {
        SREG = s;
    }
}
//------------------------------------------------------------------------------
/** Read pin value.
 * @param[in] pin Arduino pin number
 * @return value read
 */
static inline __attribute__((always_inline)) bool fastDigitalRead(uint8_t pin)
{
    return *pinReg(pin) & pinMask(pin);
}
//------------------------------------------------------------------------------
/** Toggle a pin.
 * @param[in] pin Arduino pin number
 *
 * If the pin is in output mode toggle the pin level.
 * If the pin is in input mode toggle the state of the 20K pullup.
 */
static inline __attribute__((always_inline)) void fastDigitalToggle(uint8_t pin)
{
    if (pinReg(pin) > reinterpret_cast<uint8_t *>(0X3F))
    {
        // must write bit to high address port
        *pinReg(pin) = pinMask(pin);
    }
    else
    {
        // will compile to sbi and PIN register will not be read.
        *pinReg(pin) |= pinMask(pin);
    }
}
//------------------------------------------------------------------------------
/** Set pin value.
 * @param[in] pin Arduino pin number
 * @param[in] level value to write
 */
static inline __attribute__((always_inline)) void fastDigitalWrite(uint8_t pin, bool level)
{
    fastBitWriteSafe(portReg(pin), pinMask(pin), level);
}
//------------------------------------------------------------------------------
/** Write the DDR register.
 * @param[in] pin Arduino pin number
 * @param[in] level value to write
 */
static inline __attribute__((always_inline)) void fastDdrWrite(uint8_t pin, bool level)
{
    fastBitWriteSafe(ddrReg(pin), pinMask(pin), level);
}
//------------------------------------------------------------------------------
/** Set pin mode.
 * @param[in] pin Arduino pin number
 * @param[in] mode INPUT, OUTPUT, or INPUT_PULLUP.
 *
 * The internal pullup resistors will be enabled if mode is INPUT_PULLUP
 * and disabled if the mode is INPUT.
 */
static inline __attribute__((always_inline)) void fastPinMode(uint8_t pin, uint8_t mode)
{
    fastDdrWrite(pin, mode == OUTPUT);
    if (mode != OUTPUT)
    {
        fastDigitalWrite(pin, mode == INPUT_PULLUP);
    }
}
#else // defined(__AVR__)
#if defined(CORE_TEENSY)
//------------------------------------------------------------------------------
/** read pin value
 * @param[in] pin Arduino pin number
 * @return value read
 */
static inline __attribute__((always_inline)) bool fastDigitalRead(uint8_t pin)
{
    return *portInputRegister(pin);
}
//------------------------------------------------------------------------------
/** Set pin value
 * @param[in] pin Arduino pin number
 * @param[in] level value to write
 */
static inline __attribute__((always_inline)) void fastDigitalWrite(uint8_t pin, bool value)
{
    if (value)
    {
        *portSetRegister(pin) = 1;
    }
    else
    {
        *portClearRegister(pin) = 1;
    }
}
#elif defined(__SAM3X8E__) || defined(__SAM3X8H__)
//------------------------------------------------------------------------------
/** read pin value
 * @param[in] pin Arduino pin number
 * @return value read
 */
static inline __attribute__((always_inline)) bool fastDigitalRead(uint8_t pin)
{
    return g_APinDescription[pin].pPort->PIO_PDSR & g_APinDescription[pin].ulPin;
}
//------------------------------------------------------------------------------
/** Set pin value
 * @param[in] pin Arduino pin number
 * @param[in] level value to write
 */
static inline __attribute__((always_inline)) void fastDigitalWrite(uint8_t pin, bool value)
{
    if (value)
    {
        g_APinDescription[pin].pPort->PIO_SODR = g_APinDescription[pin].ulPin;
    }
    else
    {
        g_APinDescription[pin].pPort->PIO_CODR = g_APinDescription[pin].ulPin;
    }
}
#elif defined(ESP8266)
//------------------------------------------------------------------------------
/** Set pin value
 * @param[in] pin Arduino pin number
 * @param[in] val value to write
 */
static inline __attribute__((always_inline)) void fastDigitalWrite(uint8_t pin, uint8_t val)
{
    if (pin < 16)
    {
        if (val)
        {
            GPOS = (1 << pin);
        }
        else
        {
            GPOC = (1 << pin);
        }
    }
    else if (pin == 16)
    {
        if (val)
        {
            GP16O |= 1;
        }
        else
        {
            GP16O &= ~1;
        }
    }
}
//------------------------------------------------------------------------------
/** Read pin value
 * @param[in] pin Arduino pin number
 * @return value read
 */
static inline __attribute__((always_inline)) bool fastDigitalRead(uint8_t pin)
{
    if (pin < 16)
    {
        return GPIP(pin);
    }
    else if (pin == 16)
    {
        return GP16I & 0x01;
    }
    return 0;
}
#else  // CORE_TEENSY
//------------------------------------------------------------------------------
inline void fastDigitalWrite(uint8_t pin, bool value)
{
    digitalWrite(pin, value);
}
//------------------------------------------------------------------------------
inline bool fastDigitalRead(uint8_t pin)
{
    return digitalRead(pin);
}
#endif // CORE_TEENSY
//------------------------------------------------------------------------------
inline void fastDigitalToggle(uint8_t pin)
{
    fastDigitalWrite(pin, !fastDigitalRead(pin));
}
//------------------------------------------------------------------------------
inline void fastPinMode(uint8_t pin, uint8_t mode)
{
    pinMode(pin, mode);
}
#endif // __AVR__
//------------------------------------------------------------------------------
/** set pin configuration
 * @param[in] pin Arduino pin number
 * @param[in] mode mode INPUT or OUTPUT.
 * @param[in] level If mode is output, set level high/low.
 *                  If mode is input, enable or disable the pin's 20K pullup.
 */
#define fastPinConfig(pin, mode, level)                                                                                \
    {                                                                                                                  \
        fastPinMode(pin, mode);                                                                                        \
        fastDigitalWrite(pin, level);                                                                                  \
    }
//==============================================================================
/**
 * @class DigitalPin
 * @brief Fast digital port I/O
 */
template <uint8_t PinNumber> class DigitalPin
{
  public:
    //----------------------------------------------------------------------------
    /** Constructor */
    DigitalPin()
    {
    }
    //----------------------------------------------------------------------------
    /** Asignment operator.
     * @param[in] value If true set the pin's level high else set the
     *  pin's level low.
     *
     * @return This DigitalPin instance.
     */
    inline DigitalPin &operator=(bool value) __attribute__((always_inline))
    {
        write(value);
        return *this;
    }
    //----------------------------------------------------------------------------
    /** Parenthesis operator.
     * @return Pin's level
     */
    inline operator bool() const __attribute__((always_inline))
    {
        return read();
    }
    //----------------------------------------------------------------------------
    /** Set pin configuration.
     * @param[in] mode: INPUT or OUTPUT.
     * @param[in] level If mode is OUTPUT, set level high/low.
     *                  If mode is INPUT, enable or disable the pin's 20K pullup.
     */
    inline __attribute__((always_inline)) void config(uint8_t mode, bool level)
    {
        fastPinConfig(PinNumber, mode, level);
    }
    //----------------------------------------------------------------------------
    /**
     * Set pin level high if output mode or enable 20K pullup if input mode.
     */
    inline __attribute__((always_inline)) void high()
    {
        write(true);
    }
    //----------------------------------------------------------------------------
    /**
     * Set pin level low if output mode or disable 20K pullup if input mode.
     */
    inline __attribute__((always_inline)) void low()
    {
        write(false);
    }
    //----------------------------------------------------------------------------
    /**
     * Set pin mode.
     * @param[in] mode: INPUT, OUTPUT, or INPUT_PULLUP.
     *
     * The internal pullup resistors will be enabled if mode is INPUT_PULLUP
     * and disabled if the mode is INPUT.
     */
    inline __attribute__((always_inline)) void mode(uint8_t mode)
    {
        fastPinMode(PinNumber, mode);
    }
    //----------------------------------------------------------------------------
    /** @return Pin's level. */
    inline __attribute__((always_inline)) bool read() const
    {
        return fastDigitalRead(PinNumber);
    }
    //----------------------------------------------------------------------------
    /** Toggle a pin.
     *
     * If the pin is in output mode toggle the pin's level.
     * If the pin is in input mode toggle the state of the 20K pullup.
     */
    inline __attribute__((always_inline)) void toggle()
    {
        fastDigitalToggle(PinNumber);
    }
    //----------------------------------------------------------------------------
    /** Write the pin's level.
     * @param[in] value If true set the pin's level high else set the
     *  pin's level low.
     */
    inline __attribute__((always_inline)) void write(bool value)
    {
        fastDigitalWrite(PinNumber, value);
    }
};
#endif // DigitalPin_h
/** @} */
