//---------------------------------------------------------------------------
//
//	SCSI Target Emulator PiSCSI
//	for Raspberry Pi (And Banana Pi)
//
//  Copyright (c) 2012-2015 Ben Croston
//  Copyright (C) 2022 akuker
//
//  Large portions of this functionality were derived from c_gpio.c, which
//  is part of the RPI.GPIO library available here:
//     https://github.com/BPI-SINOVOIP/RPi.GPIO/blob/master/source/c_gpio.c
//
//  Permission is hereby granted, free of charge, to any person obtaining a copy of
//  this software and associated documentation files (the "Software"), to deal in
//  the Software without restriction, including without limitation the rights to
//  use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
//  of the Software, and to permit persons to whom the Software is furnished to do
//  so, subject to the following conditions:
//
//  The above copyright notice and this permission notice shall be included in all
//  copies or substantial portions of the Software.
//
//  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
//  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
//  SOFTWARE.
//
//---------------------------------------------------------------------------

#include <iomanip>
#include <memory>
#include <sstream>
#include <string.h>
#include <sys/epoll.h>
#include <sys/ioctl.h>
#include <sys/mman.h>

#include "hal/gpiobus.h"
#include "hal/gpiobus_bananam2p.h"
#include "hal/pi_defs/bpi-gpio.h"
#include "hal/sunxi_utils.h"
#include "hal/systimer.h"
#include "shared/log.h"

#define ARRAY_SIZE(x) (sizeof(x) / (sizeof(x[0])))

bool GPIOBUS_BananaM2p::Init(mode_e mode)
{
    GPIO_FUNCTION_TRACE
    GPIOBUS::Init(mode);
    SysTimer::Init();

    sbc_version = SBC_Version::GetSbcVersion();

    for (auto const gpio_num : SignalTable) {
        if (gpio_num == -1) {
            break;
        }

        int gpio_bank = SunXI::GPIO_BANK(gpio_num);

        if (std::find(gpio_banks.begin(), gpio_banks.end(), gpio_bank) != gpio_banks.end()) {
            LOGTRACE("Duplicate bank: %d", gpio_bank)

        } else {
            LOGDEBUG("New bank: %d", gpio_bank)
            gpio_banks.push_back(gpio_bank);
        }
    }

    if (int result = sunxi_setup(); result != SETUP_OK) {
        return false;
    }

    InitializeGpio();

    MakeTable();

    // SetupSelEvent needs to be called AFTER Initialize GPIO. This function
    // reconfigures the SEL signal.
    if (!SetupSelEvent()) {
        LOGERROR("Failed to setup SELECT poll event")
        return false;
    }
    LOGTRACE("SetupSelEvent OK!")

    // Set drive strength to maximum
    DrvConfig(3);

    return true;
}

void GPIOBUS_BananaM2p::InitializeGpio()
{
    GPIO_FUNCTION_TRACE

    // Set pull up/pull down
#if SIGNAL_CONTROL_MODE == 0
    int pullmode = GPIO_PULLNONE;
#elif SIGNAL_CONTROL_MODE == 1
    int pullmode = GPIO_PULLUP;
#else
    int pullmode = GPIO_PULLDOWN;
#endif

    // Initialize all signals
    for (int i = 0; SignalTable[i] >= 0; i++) {
        int j = SignalTable[i];
        PinConfig(j, GPIO_INPUT);
        PullConfig(j, pullmode);
        PinSetSignal(j, OFF);
    }

    // Set control signals
    PinConfig(BPI_PIN_ACT, GPIO_OUTPUT);
    PinConfig(BPI_PIN_TAD, GPIO_OUTPUT);
    PinConfig(BPI_PIN_IND, GPIO_OUTPUT);
    PinConfig(BPI_PIN_DTD, GPIO_OUTPUT);
    PinSetSignal(BPI_PIN_ACT, OFF);
    PinSetSignal(BPI_PIN_TAD, OFF);
    PinSetSignal(BPI_PIN_IND, OFF);
    PinSetSignal(BPI_PIN_DTD, OFF);

    // Set the ENABLE signal
    // This is used to show that the application is running
    PinConfig(BPI_PIN_ENB, GPIO_OUTPUT);
    PinSetSignal(BPI_PIN_ENB, ON);
}

void GPIOBUS_BananaM2p::Cleanup()
{
    GPIO_FUNCTION_TRACE
#if defined(__x86_64__) || defined(__X86__) || defined(__aarch64__)
    dummy_var--; // Need to do something to prevent Sonar from claiming this should be a const function
    return;
#else

#ifdef USE_SEL_EVENT_ENABLE
    // Release SEL signal interrupt
    close(selevreq.fd);
#endif // USE_SEL_EVENT_ENABLE

    // Set control signals
    PinConfig(BPI_PIN_ACT, GPIO_INPUT);
    PinConfig(BPI_PIN_TAD, GPIO_INPUT);
    PinConfig(BPI_PIN_IND, GPIO_INPUT);
    PinConfig(BPI_PIN_DTD, GPIO_INPUT);
    PinSetSignal(BPI_PIN_ENB, OFF);
    PinSetSignal(BPI_PIN_ACT, OFF);
    PinSetSignal(BPI_PIN_TAD, OFF);
    PinSetSignal(BPI_PIN_IND, OFF);
    PinSetSignal(BPI_PIN_DTD, OFF);

    // Initialize all signals
    for (int i = 0; SignalTable[i] >= 0; i++) {
        int pin = SignalTable[i];
        PinConfig(pin, GPIO_INPUT);
        PullConfig(pin, GPIO_PULLNONE);
        PinSetSignal(pin, OFF);
    }

    // Set drive strength back to Default (Level 1)
    DrvConfig(1);

    munmap((void *)gpio_map, SunXI::BLOCK_SIZE);
    munmap((void *)r_gpio_map, SunXI::BLOCK_SIZE);

#endif
}

void GPIOBUS_BananaM2p::Reset()
{
#if defined(__x86_64__) || defined(__X86__) || defined(__aarch64__)
    dummy_var++;
    return;
#else
    int i;
    int j;

    // Turn off active signal
    SetControl(BPI_PIN_ACT, ACT_OFF);

    // Set all signals to off
    for (i = 0;; i++) {
        j = SignalTable[i];
        if (j < 0) {
            break;
        }

        SetSignal(j, OFF);
    }

    if (actmode == mode_e::TARGET) {
        // Target mode

        // Set target signal to input
        SetControl(BPI_PIN_TAD, TAD_IN);
        SetMode(BPI_PIN_BSY, IN);
        SetMode(BPI_PIN_MSG, IN);
        SetMode(BPI_PIN_CD, IN);
        SetMode(BPI_PIN_REQ, IN);
        SetMode(BPI_PIN_IO, IN);

        // Set the initiator signal to input
        SetControl(BPI_PIN_IND, IND_IN);
        SetMode(BPI_PIN_SEL, IN);
        SetMode(BPI_PIN_ATN, IN);
        SetMode(BPI_PIN_ACK, IN);
        SetMode(BPI_PIN_RST, IN);

        // Set data bus signals to input
        SetControl(BPI_PIN_DTD, DTD_IN);
        SetMode(BPI_PIN_DT0, IN);
        SetMode(BPI_PIN_DT1, IN);
        SetMode(BPI_PIN_DT2, IN);
        SetMode(BPI_PIN_DT3, IN);
        SetMode(BPI_PIN_DT4, IN);
        SetMode(BPI_PIN_DT5, IN);
        SetMode(BPI_PIN_DT6, IN);
        SetMode(BPI_PIN_DT7, IN);
        SetMode(BPI_PIN_DP, IN);
    } else {
        // Initiator mode

        // Set target signal to input
        SetControl(BPI_PIN_TAD, TAD_IN);
        SetMode(BPI_PIN_BSY, IN);
        SetMode(BPI_PIN_MSG, IN);
        SetMode(BPI_PIN_CD, IN);
        SetMode(BPI_PIN_REQ, IN);
        SetMode(BPI_PIN_IO, IN);

        // Set the initiator signal to output
        SetControl(BPI_PIN_IND, IND_OUT);
        SetMode(BPI_PIN_SEL, OUT);
        SetMode(BPI_PIN_ATN, OUT);
        SetMode(BPI_PIN_ACK, OUT);
        SetMode(BPI_PIN_RST, OUT);

        // Set the data bus signals to output
        SetControl(BPI_PIN_DTD, DTD_OUT);
        SetMode(BPI_PIN_DT0, OUT);
        SetMode(BPI_PIN_DT1, OUT);
        SetMode(BPI_PIN_DT2, OUT);
        SetMode(BPI_PIN_DT3, OUT);
        SetMode(BPI_PIN_DT4, OUT);
        SetMode(BPI_PIN_DT5, OUT);
        SetMode(BPI_PIN_DT6, OUT);
        SetMode(BPI_PIN_DT7, OUT);
        SetMode(BPI_PIN_DP, OUT);
    }

    // Initialize all signals
    // TODO!! For now, just re-run Acquire
    Acquire();
#endif // ifdef __x86_64__ || __X86__
}

bool GPIOBUS_BananaM2p::SetupSelEvent()
{
#if defined(__x86_64__) || defined(__X86__) || defined(__aarch64__)
    dummy_var += 2; // Need to do something to prevent Sonar from claiming this should be a const function
    return false;
#else
    GPIO_FUNCTION_TRACE
    int gpio_pin = BPI_PIN_SEL;

    // GPIO chip open
    LOGTRACE("%s GPIO chip open  [%d]", __PRETTY_FUNCTION__, gpio_pin)
    std::string gpio_dev = "/dev/gpiochip0";
    if (SunXI::GPIO_BANK(gpio_pin) >= 11) {
        gpio_dev = "/dev/gpiochip1";
        LOGWARN("gpiochip1 support isn't implemented yet....")
        LOGWARN("THIS PROBABLY WONT WORK!")
    }

    int gpio_fd = open(gpio_dev.c_str(), 0);
    if (gpio_fd == -1) {
        LOGERROR("Unable to open /dev/gpiochip0. Is PiSCSI or RaSCSI already running?")
        return false;
    }

    // Event request setting
    LOGTRACE("%s Event request setting (pin sel: %d)", __PRETTY_FUNCTION__, gpio_pin)
    strncpy(selevreq.consumer_label, "PiSCSI", ARRAY_SIZE(selevreq.consumer_label));
    selevreq.lineoffset  = gpio_pin;
    selevreq.handleflags = GPIOHANDLE_REQUEST_INPUT;
#if SIGNAL_CONTROL_MODE < 2
    selevreq.eventflags  = GPIOEVENT_REQUEST_FALLING_EDGE;
    LOGTRACE("%s eventflags = GPIOEVENT_REQUEST_FALLING_EDGE", __PRETTY_FUNCTION__)
#else
    selevreq.eventflags = GPIOEVENT_REQUEST_RISING_EDGE;
    LOGTRACE("%s eventflags = GPIOEVENT_REQUEST_RISING_EDGE", __PRETTY_FUNCTION__)
#endif // SIGNAL_CONTROL_MODE

    // Get event request
    if (ioctl(gpio_fd, GPIO_GET_LINEEVENT_IOCTL, &selevreq) == -1) {
        LOGERROR("selevreq.fd = %d %08X", selevreq.fd, (unsigned int)selevreq.fd)
        LOGERROR("Unable to register event request. Is PiSCSI or RaSCSI already running?")
        LOGERROR("[%08X] %s", errno, strerror(errno))
        close(gpio_fd);
        return false;
    }

    // Close GPIO chip file handle
    LOGTRACE("%s Close GPIO chip file handle", __PRETTY_FUNCTION__)
    close(gpio_fd);

    // epoll initialization
    LOGTRACE("%s epoll initialization", __PRETTY_FUNCTION__)
    epfd = epoll_create(1);
    if (epfd == -1) {
        LOGERROR("Unable to create the epoll event")
        return false;
    }
    epoll_event ev = {};
    memset(&ev, 0, sizeof(ev));
    ev.events  = EPOLLIN | EPOLLPRI;
    ev.data.fd = selevreq.fd;
    if (epoll_ctl(epfd, EPOLL_CTL_ADD, selevreq.fd, &ev) < 0) {
        return false;
    }

    return true;
#endif
}

void GPIOBUS_BananaM2p::SetENB(bool ast)
{
    PinSetSignal(BPI_PIN_ENB, ast ? ENB_ON : ENB_OFF);
}

bool GPIOBUS_BananaM2p::GetBSY() const
{
    return GetSignal(BPI_PIN_BSY);
}

void GPIOBUS_BananaM2p::SetBSY(bool ast)
{
    if (actmode == mode_e::TARGET) {
        if (ast) {
            // Turn on ACTIVE signal
            SetControl(BPI_PIN_ACT, ACT_ON);

            // Set Target signal to output
            SetControl(BPI_PIN_TAD, TAD_OUT);

            SetMode(BPI_PIN_BSY, OUT);
            SetMode(BPI_PIN_MSG, OUT);
            SetMode(BPI_PIN_CD, OUT);
            SetMode(BPI_PIN_REQ, OUT);
            SetMode(BPI_PIN_IO, OUT);

            // Set BSY signal
            SetSignal(BPI_PIN_BSY, ast);

        } else {
            // Turn off the ACTIVE signal
            SetControl(BPI_PIN_ACT, ACT_OFF);

            // Set the target signal to input
            SetControl(BPI_PIN_TAD, TAD_IN);

            SetMode(BPI_PIN_BSY, IN);
            SetMode(BPI_PIN_MSG, IN);
            SetMode(BPI_PIN_CD, IN);
            SetMode(BPI_PIN_REQ, IN);
            SetMode(BPI_PIN_IO, IN);
        }
    } else {
        // Set BSY signal
        SetSignal(BPI_PIN_BSY, ast);
    }
}

bool GPIOBUS_BananaM2p::GetSEL() const
{
    return GetSignal(BPI_PIN_SEL);
}

void GPIOBUS_BananaM2p::SetSEL(bool ast)
{
    if (actmode == mode_e::INITIATOR && ast) {
        // Turn on ACTIVE signal
        SetControl(BPI_PIN_ACT, ACT_ON);
    }

    // Set SEL signal
    SetSignal(BPI_PIN_SEL, ast);
}

bool GPIOBUS_BananaM2p::GetATN() const
{
    return GetSignal(BPI_PIN_ATN);
}

void GPIOBUS_BananaM2p::SetATN(bool ast)
{
    SetSignal(BPI_PIN_ATN, ast);
}

bool GPIOBUS_BananaM2p::GetACK() const
{
    return GetSignal(BPI_PIN_ACK);
}

void GPIOBUS_BananaM2p::SetACK(bool ast)
{
    SetSignal(BPI_PIN_ACK, ast);
}

bool GPIOBUS_BananaM2p::GetACT() const
{
    return GetSignal(BPI_PIN_ACT);
}

void GPIOBUS_BananaM2p::SetACT(bool ast)
{
    SetSignal(BPI_PIN_ACT, ast);
}

bool GPIOBUS_BananaM2p::GetRST() const
{
    return GetSignal(BPI_PIN_RST);
}

void GPIOBUS_BananaM2p::SetRST(bool ast)
{
    SetSignal(BPI_PIN_RST, ast);
}

bool GPIOBUS_BananaM2p::GetMSG() const
{
    return GetSignal(BPI_PIN_MSG);
}

void GPIOBUS_BananaM2p::SetMSG(bool ast)
{
    SetSignal(BPI_PIN_MSG, ast);
}

bool GPIOBUS_BananaM2p::GetCD() const
{
    return GetSignal(BPI_PIN_CD);
}

void GPIOBUS_BananaM2p::SetCD(bool ast)
{
    SetSignal(BPI_PIN_CD, ast);
}

bool GPIOBUS_BananaM2p::GetIO()
{
    bool ast = GetSignal(BPI_PIN_IO);

    if (actmode == mode_e::INITIATOR) {
        // Change the data input/output direction by IO signal
        if (ast) {
            SetControl(BPI_PIN_DTD, DTD_IN);
            SetMode(BPI_PIN_DT0, IN);
            SetMode(BPI_PIN_DT1, IN);
            SetMode(BPI_PIN_DT2, IN);
            SetMode(BPI_PIN_DT3, IN);
            SetMode(BPI_PIN_DT4, IN);
            SetMode(BPI_PIN_DT5, IN);
            SetMode(BPI_PIN_DT6, IN);
            SetMode(BPI_PIN_DT7, IN);
            SetMode(BPI_PIN_DP, IN);
        } else {
            SetControl(BPI_PIN_DTD, DTD_OUT);
            SetMode(BPI_PIN_DT0, OUT);
            SetMode(BPI_PIN_DT1, OUT);
            SetMode(BPI_PIN_DT2, OUT);
            SetMode(BPI_PIN_DT3, OUT);
            SetMode(BPI_PIN_DT4, OUT);
            SetMode(BPI_PIN_DT5, OUT);
            SetMode(BPI_PIN_DT6, OUT);
            SetMode(BPI_PIN_DT7, OUT);
            SetMode(BPI_PIN_DP, OUT);
        }
    }

    return ast;
}

void GPIOBUS_BananaM2p::SetIO(bool ast)
{
    if (actmode == mode_e::TARGET) {
        // Change the data input/output direction by IO signal
        if (ast) {
            SetControl(BPI_PIN_DTD, DTD_OUT);
            SetMode(BPI_PIN_DT0, OUT);
            SetMode(BPI_PIN_DT1, OUT);
            SetMode(BPI_PIN_DT2, OUT);
            SetMode(BPI_PIN_DT3, OUT);
            SetMode(BPI_PIN_DT4, OUT);
            SetMode(BPI_PIN_DT5, OUT);
            SetMode(BPI_PIN_DT6, OUT);
            SetMode(BPI_PIN_DT7, OUT);
            SetMode(BPI_PIN_DP, OUT);

            SetDAT(0);
            SetSignal(BPI_PIN_IO, ast);

        } else {
            SetControl(BPI_PIN_DTD, DTD_IN);
            SetMode(BPI_PIN_DT0, IN);
            SetMode(BPI_PIN_DT1, IN);
            SetMode(BPI_PIN_DT2, IN);
            SetMode(BPI_PIN_DT3, IN);
            SetMode(BPI_PIN_DT4, IN);
            SetMode(BPI_PIN_DT5, IN);
            SetMode(BPI_PIN_DT6, IN);
            SetMode(BPI_PIN_DT7, IN);
            SetMode(BPI_PIN_DP, IN);
        }
    } else {
        SetSignal(BPI_PIN_IO, ast);
    }
}

bool GPIOBUS_BananaM2p::GetREQ() const
{
    return GetSignal(BPI_PIN_REQ);
}

void GPIOBUS_BananaM2p::SetREQ(bool ast)
{
    SetSignal(BPI_PIN_REQ, ast);
}

uint8_t GPIOBUS_BananaM2p::GetDAT()
{
    GPIO_FUNCTION_TRACE

    Acquire();
    uint32_t data =
        ((GetSignal(BPI_PIN_DT0) ? 0x01 : 0x00) << 0) | ((GetSignal(BPI_PIN_DT1) ? 0x01 : 0x00) << 1) |
        ((GetSignal(BPI_PIN_DT2) ? 0x01 : 0x00) << 2) | ((GetSignal(BPI_PIN_DT3) ? 0x01 : 0x00) << 3) |
        ((GetSignal(BPI_PIN_DT4) ? 0x01 : 0x00) << 4) | ((GetSignal(BPI_PIN_DT5) ? 0x01 : 0x00) << 5) |
        ((GetSignal(BPI_PIN_DT6) ? 0x01 : 0x00) << 6) |
        ((GetSignal(BPI_PIN_DT7) ? 0x01 : 0x00) << 7); // NOSONAR: GCC 10 doesn't support shift operations on std::byte

    return (uint8_t)(data & 0xFF);
}

void GPIOBUS_BananaM2p::SetDAT(uint8_t dat)
{
    GPIO_FUNCTION_TRACE

    array<uint32_t, 12> gpio_reg_values = {0};

    for (size_t gpio_num = 0; gpio_num < pintbl.size(); gpio_num++) {
        bool value;
        if (gpio_num < 8) {
            // data bits
            value = !(dat & (1 << gpio_num)); // NOSONAR: GCC 10 doesn't support shift operations on std::byte
        } else {
            // parity bit
            value = (__builtin_parity(dat) == 1);
        }

        if (value) {
            uint32_t this_gpio = pintbl[gpio_num];
            int bank           = SunXI::GPIO_BANK(this_gpio);
            int offset         = SunXI::GPIO_NUM(this_gpio);
            gpio_reg_values[bank] |= (1 << offset);
        }
    }

    sunxi_set_all_gpios(gpio_data_masks, gpio_reg_values);
}

//---------------------------------------------------------------------------
//
//	Signal table
//
//---------------------------------------------------------------------------
const array<int, 19> GPIOBUS_BananaM2p::SignalTable = {BPI_PIN_DT0, BPI_PIN_DT1, BPI_PIN_DT2, BPI_PIN_DT3, BPI_PIN_DT4,
                                                       BPI_PIN_DT5, BPI_PIN_DT6, BPI_PIN_DT7, BPI_PIN_DP,  BPI_PIN_SEL,
                                                       BPI_PIN_ATN, BPI_PIN_RST, BPI_PIN_ACK, BPI_PIN_BSY, BPI_PIN_MSG,
                                                       BPI_PIN_CD,  BPI_PIN_IO,  BPI_PIN_REQ, -1};

//---------------------------------------------------------------------------
//
//	Create work table
//
//---------------------------------------------------------------------------
void GPIOBUS_BananaM2p::MakeTable(void)
{
    for (auto this_gpio : pintbl) {
        int bank   = SunXI::GPIO_BANK(this_gpio);
        int offset = (SunXI::GPIO_NUM(this_gpio));

        gpio_data_masks[bank] |= (1 << offset);
    }
}

bool GPIOBUS_BananaM2p::GetDP() const
{
    return GetSignal(BPI_PIN_DP);
}

void GPIOBUS_BananaM2p::SetControl(int pin, bool ast)
{
    GPIO_FUNCTION_TRACE
    PinSetSignal(pin, ast);
}

// Set direction
int GPIOBUS_BananaM2p::GetMode(int pin)
{
    GPIO_FUNCTION_TRACE

    uint32_t regval = 0;
    int bank        = SunXI::GPIO_BANK(pin);
    int index       = SunXI::GPIO_CFG_INDEX(pin);
    int offset      = SunXI::GPIO_CFG_OFFSET(pin);

    volatile SunXI::sunxi_gpio_t *pio = &(pio_map->gpio_bank[bank]);
    /* DK, for PL and PM */
    if (bank >= 11) {
        bank -= 11;
        pio = &(r_pio_map->gpio_bank[bank]);
    }

    regval = pio->CFG[0 + index];

    // Extract the CFG field
    regval &= (0x7 << offset); // 0xf?
    // Shift it down to the LSB
    regval >>= offset;
    return (int)regval;
}

// Set direction
void GPIOBUS_BananaM2p::SetMode(int pin, int mode)
{
    GPIO_FUNCTION_TRACE
    int direction = mode;

    uint32_t regval = 0;
    int bank        = SunXI::GPIO_BANK(pin);       // gpio >> 5
    int index       = SunXI::GPIO_CFG_INDEX(pin);  // (gpio & 0x1F) >> 3
    int offset      = SunXI::GPIO_CFG_OFFSET(pin); // ((gpio & 0x1F) & 0x7) << 2
    LOGTRACE("%s gpio(%d) bank(%d) index(%d) offset(%d) dir(%s)", __PRETTY_FUNCTION__, pin, bank, index, offset,
             (GPIO_INPUT == direction)    ? "IN"
             : (GPIO_IRQ_IN == direction) ? "IRQ"
                                          : "OUT")

    volatile SunXI::sunxi_gpio_t *pio = &(pio_map->gpio_bank[bank]);
    /* DK, for PL and PM */
    if (bank >= 11) {
        bank -= 11;
        pio = &(r_pio_map->gpio_bank[bank]);
    }

    regval = pio->CFG[0 + index];

    // Clear the cfg field
    regval &= ~(0x7 << offset); // 0xf?
    if (GPIO_INPUT == direction) {
        regval |= (((uint32_t)SunXI::gpio_configure_values_e::gpio_input) << offset);
    } else if (GPIO_OUTPUT == direction) {
        regval |= (((uint32_t)SunXI::gpio_configure_values_e::gpio_output) << offset);
    } else if (GPIO_IRQ_IN == direction) {
        regval |= (((uint32_t)SunXI::gpio_configure_values_e::gpio_interupt) << offset);
    } else {
        LOGERROR("line:%d gpio number error %d", __LINE__, pin)
    }
    pio->CFG[0 + index] = regval;
}

bool GPIOBUS_BananaM2p::GetSignal(int pin) const
{
    GPIO_FUNCTION_TRACE
    int gpio_num = pin;

    uint32_t regval = 0;
    int bank        = SunXI::GPIO_BANK(gpio_num); // gpio >> 5
    int num         = SunXI::GPIO_NUM(gpio_num);  // gpio & 0x1F

    regval = (signals[bank] >> num) & 0x1;
    return regval != 0;
}

void GPIOBUS_BananaM2p::SetSignal(int pin, bool ast)
{
    GPIO_FUNCTION_TRACE
    int gpio_num = pin;

#if SIGNAL_CONTROL_MODE == 0
    //	  True  : 0V
    //	  False : Open collector output (disconnect from bus)
    int sunxi_gpio_state = (ast == true) ? SunXI::HIGH : SunXI::LOW;
#elif SIGNAL_CONTROL_MODE == 1
    //	  True  : 0V   -> (CONVERT) -> 0V
    //	  False : 3.3V -> (CONVERT) -> Open collector output
    LOGWARN("%s:%d THIS LOGIC NEEDS TO BE VALIDATED/TESTED", __PRETTY_FUNCTION__, __LINE__)
    int sunxi_gpio_state = (ast == true) ? SunXI::HIGH : SunXI::LOW;
#elif SIGNAL_CONTROL_MODE == 2
    //	  True  : 3.3V -> (CONVERT) -> 0V
    //	  False : 0V   -> (CONVERT) -> Open collector output
    LOGWARN("%s:%d THIS LOGIC NEEDS TO BE VALIDATED/TESTED", __PRETTY_FUNCTION__, __LINE__)
    int sunxi_gpio_state = (ast == true) ? SunXI::LOW : SunXI::HIGH;
#endif // SIGNAL_CONTROL_MODE

    LOGTRACE("gpio(%d) sunxi_state(%d)", gpio_num, sunxi_gpio_state)

    int bank = SunXI::GPIO_BANK(gpio_num); // gpio >> 5
    int num  = SunXI::GPIO_NUM(gpio_num);  // gpio & 0x1F

    volatile SunXI::sunxi_gpio_t *pio = &(pio_map->gpio_bank[bank]);

    /* DK, for PL and PM */
    if (bank >= 11) {
        LOGTRACE("bank > 11")
        bank -= 11;
        pio = &(r_pio_map->gpio_bank[bank]);
    }

    if (sunxi_gpio_state == SunXI::HIGH)
        pio->DAT &= ~(1 << num);
    else
        pio->DAT |= (1 << num);
}

void GPIOBUS_BananaM2p::DisableIRQ()
{
    *tmr_ctrl = 0b00;
}

void GPIOBUS_BananaM2p::EnableIRQ()
{
    *tmr_ctrl = 0b11;
}

void GPIOBUS_BananaM2p::PinConfig(int pin, int mode)
{
    GPIO_FUNCTION_TRACE
    int gpio_num        = pin;
    int sunxi_direction = (mode == GPIO_INPUT) ? SunXI::INPUT : SunXI::OUTPUT;

    sunxi_setup_gpio(gpio_num, sunxi_direction, -1);
}

void GPIOBUS_BananaM2p::PullConfig(int pin, int mode)
{
    GPIO_FUNCTION_TRACE
#if defined(__x86_64__) || defined(__X86__) || defined(__aarch64__)
    dummy_var++; // Need to do something to prevent Sonar from claiming this should be a const function
    (void)pin;
    (void)mode;
    return;
#else

    // Note: this will throw an exception if an invalid pin is specified
    int gpio_num           = pin;
    int pull_up_down_state = 0;

    switch (mode) {
    case GPIO_PULLNONE:
        pull_up_down_state = SunXI::PUD_OFF;
        break;
    case GPIO_PULLUP:
        pull_up_down_state = SunXI::PUD_UP;
        break;
    case GPIO_PULLDOWN:
        pull_up_down_state = SunXI::PUD_DOWN;
        break;
    default:
        LOGERROR("%s INVALID PIN MODE", __PRETTY_FUNCTION__);
        return;
    }

    uint32_t regval = 0;
    int bank        = SunXI::GPIO_BANK(gpio_num);       // gpio >> 5
    int index       = SunXI::GPIO_PUL_INDEX(gpio_num);  // (gpio & 0x1f) >> 4
    int offset      = SunXI::GPIO_PUL_OFFSET(gpio_num); // (gpio) & 0x0F) << 1
    LOGTRACE("%s gpio(%d) bank(%d) index(%d) offset(%d)", __PRETTY_FUNCTION__, gpio_num, bank, index, offset)

    volatile SunXI::sunxi_gpio_t *pio = &(pio_map->gpio_bank[bank]);
    /* DK, for PL and PM */
    if (bank >= 11) {
        bank -= 11;
        pio = &(r_pio_map->gpio_bank[bank]);
    }

    regval = *(&pio->PULL[0] + index);
    regval &= ~(3 << offset);
    regval |= pull_up_down_state << offset;
    pio->PULL[0 + index] = regval;
#endif
}

void GPIOBUS_BananaM2p::PinSetSignal(int pin, bool ast)
{
    GPIO_FUNCTION_TRACE
    int gpio_num = pin;

    int sunxi_gpio_state = (ast == true) ? SunXI::HIGH : SunXI::LOW;
    sunxi_output_gpio(gpio_num, sunxi_gpio_state);
}

void GPIOBUS_BananaM2p::DrvConfig(uint32_t drive)
{
    GPIO_FUNCTION_TRACE

    for (auto pin : SignalTable) {
        if (pin == -1) {
            continue;
        }

        LOGTRACE("Configuring GPIO %d to drive strength %d", pin, drive)

        uint32_t regval = 0;
        int bank        = SunXI::GPIO_BANK(pin);       // gpio >> 5
        int index       = SunXI::GPIO_DRV_INDEX(pin);  // (gpio & 0x1F) >> 3
        int offset      = SunXI::GPIO_DRV_OFFSET(pin); // ((gpio & 0x1F) & 0x7) << 2
        LOGTRACE("%s gpio(%d) bank(%d) index(%d) offset(%d)", __PRETTY_FUNCTION__, pin, bank, index, offset)

        volatile SunXI::sunxi_gpio_t *pio = &(pio_map->gpio_bank[bank]);
        /* DK, for PL and PM */
        if (bank >= 11) {
            bank -= 11;
            pio = &(r_pio_map->gpio_bank[bank]);
        }

        // Get current register value
        regval = pio->DRV[0 + index];
        // Clear the DRV value for that gpio
        regval &= ~(0x7 << offset); // 0xf?
        // Set the new DRV strength
        regval |= (drive & 0b11) << offset;
        // Save back to the register
        pio->DRV[0 + index] = regval;
    }

    // #endif // if __arm__
}

uint32_t GPIOBUS_BananaM2p::Acquire()
{
    GPIO_FUNCTION_TRACE

    for (auto bank : gpio_banks) {
        volatile SunXI::sunxi_gpio_t *pio = &(pio_map->gpio_bank[bank]);
        /* DK, for PL and PM */
        if (bank >= 11) {
            pio = &(r_pio_map->gpio_bank[bank - 11]);
        }

        uint32_t regval = pio->DAT;

#if SIGNAL_CONTROL_MODE < 2
        // Invert if negative logic (internal processing is unified to positive logic)
        regval = ~regval;
#endif // SIGNAL_CONTROL_MODE
        signals[bank] = regval;
    }
    // TODO: This should do something someday....
    return 0;
}

int GPIOBUS_BananaM2p::sunxi_setup(void)
{
    GPIO_FUNCTION_TRACE
#if defined(__x86_64__) || defined(__X86__) || defined(__aarch64__)
    dummy_var++; // Need to do something to prevent Sonar from claiming this should be a const function
    return SunXI::SETUP_MMAP_FAIL;
#else
    int mem_fd;
    uint8_t *gpio_mem;

    // mmap the GPIO memory registers
    if ((mem_fd = open("/dev/mem", O_RDWR | O_SYNC)) < 0) {
        LOGERROR("Error: Unable to open /dev/mem. Are you running as root?")
        LOGDEBUG("errno: [%08X] %s", errno, strerror(errno));
        return SunXI::SETUP_DEVMEM_FAIL;
    }

    if ((gpio_mem = (uint8_t *)malloc(SunXI::BLOCK_SIZE + (SunXI::PAGE_SIZE - 1))) == NULL) {
        LOGERROR("Error: Unable to allocate gpio memory. Are you running as root?")
        LOGDEBUG("errno: [%08X] %s", errno, strerror(errno));
        return SunXI::SETUP_DEVMEM_FAIL;
    }

    if ((uint32_t)gpio_mem % SunXI::PAGE_SIZE)
        gpio_mem += SunXI::PAGE_SIZE - ((uint32_t)gpio_mem % SunXI::PAGE_SIZE);

    gpio_map = (uint32_t *)mmap((caddr_t)gpio_mem, SunXI::BLOCK_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_FIXED,
                                mem_fd, SunXI::SUNXI_GPIO_BASE);
    if ((void *)gpio_map == MAP_FAILED) {
        LOGERROR("Error: Unable to map gpio memory. Are you running as root?")
        LOGDEBUG("errno: [%08X] %s", errno, strerror(errno));
        return SunXI::SETUP_MMAP_FAIL;
    }
    pio_map = (volatile SunXI::sunxi_gpio_reg *)(gpio_map + (SunXI::SUNXI_GPIO_REG_OFFSET >> 2));
    LOGTRACE("gpio_mem[%p] gpio_map[%p] pio_map[%p]", gpio_mem, gpio_map, pio_map)
    // R_PIO GPIO LMN
    r_gpio_map = (uint32_t *)mmap((caddr_t)0, SunXI::BLOCK_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, mem_fd,
                                  SunXI::SUNXI_R_GPIO_BASE);
    if ((void *)r_gpio_map == MAP_FAILED) {
        LOGERROR("Error: Unable to map r_gpio memory. Are you running as root?")
        LOGDEBUG("errno: [%08X] %s", errno, strerror(errno));
        return SunXI::SETUP_MMAP_FAIL;
    }
    r_pio_map = (volatile SunXI::sunxi_gpio_reg *)(r_gpio_map + (SunXI::SUNXI_R_GPIO_REG_OFFSET >> 2));
    LOGTRACE("r_gpio_map[%p] r_pio_map[%p]", r_gpio_map, r_pio_map)

    tmr_ctrl = gpio_map + ((SunXI::TMR_REGISTER_BASE - SunXI::SUNXI_GPIO_BASE) >> 2);
    // LOGINFO("tmr_ctrl offset: %08X value: %08X", (TMR_REGISTER_BASE - SUNXI_GPIO_BASE), *tmr_ctrl);

    close(mem_fd);
    return SETUP_OK;
#endif
}

void GPIOBUS_BananaM2p::sunxi_setup_gpio(int pin, int direction, int pud)
{
    GPIO_FUNCTION_TRACE
#if defined(__x86_64__) || defined(__X86__) || defined(__aarch64__)
    dummy_var++; // Need to do something to prevent Sonar from claiming this should be a const function
    (void)pin;
    (void)direction;
    (void)pud;
    return;
#else
    uint32_t regval = 0;
    int bank        = SunXI::GPIO_BANK(pin);       // gpio >> 5
    int index       = SunXI::GPIO_CFG_INDEX(pin);  // (gpio & 0x1F) >> 3
    int offset      = SunXI::GPIO_CFG_OFFSET(pin); // ((gpio & 0x1F) & 0x7) << 2
    LOGTRACE("%s gpio(%d) bank(%d) index(%d) offset(%d)", __PRETTY_FUNCTION__, pin, bank, index, offset)

    volatile SunXI::sunxi_gpio_t *pio = &(pio_map->gpio_bank[bank]);
    /* DK, for PL and PM */
    if (bank >= 11) {
        bank -= 11;
        pio = &(r_pio_map->gpio_bank[bank]);
    }

    if (pud != -1) {
        set_pullupdn(pin, pud);
    }
    regval = *(&pio->CFG[0 + index]);
    regval &= ~(0x7 << offset); // 0xf?
    if (SunXI::INPUT == direction) {
        pio->CFG[0 + index] = regval;
    } else if (SunXI::OUTPUT == direction) {
        regval |= (1 << offset);
        pio->CFG[0 + index] = regval;
    } else {
        LOGERROR("line:%d gpio number error %d", __LINE__, pin)
    }
#endif
}

void GPIOBUS_BananaM2p::sunxi_output_gpio(int pin, int value)
{
    GPIO_FUNCTION_TRACE
    if (pin < 0) {
        LOGWARN("Invalid GPIO Num")
        return;
    }
    int bank = SunXI::GPIO_BANK(pin); // gpio >> 5
    int num  = SunXI::GPIO_NUM(pin);  // gpio & 0x1F

    LOGTRACE("%s gpio(%d) bank(%d) num(%d) value(%d)", __PRETTY_FUNCTION__, pin, bank, num, value)
    volatile SunXI::sunxi_gpio_t *pio = &(pio_map->gpio_bank[bank]);

    /* DK, for PL and PM */
    if (bank >= 11) {
        bank -= 11;
        pio = &(r_pio_map->gpio_bank[bank]);
    }

    if (value == 0)
        pio->DAT &= ~(1 << num);
    else
        pio->DAT |= (1 << num);
}

void GPIOBUS_BananaM2p::sunxi_set_all_gpios(array<uint32_t, 12> &mask, array<uint32_t, 12> &value)
{
    GPIO_FUNCTION_TRACE
    for (size_t bank = 0; bank < mask.size(); bank++) {
        if (mask[bank] == 0) {
            continue;
        }

        volatile SunXI::sunxi_gpio_t *pio;
        if (bank < 11) {
            pio = &(pio_map->gpio_bank[bank]);
        } else {
            pio = &(r_pio_map->gpio_bank[bank - 11]);
        }

        uint32_t reg_val = pio->DAT;
        reg_val &= ~mask[bank];
        reg_val |= value[bank];
        pio->DAT = reg_val;
    }
}

int GPIOBUS_BananaM2p::sunxi_input_gpio(int pin) const
{
    GPIO_FUNCTION_TRACE
    uint32_t regval = 0;
    int bank        = SunXI::GPIO_BANK(pin); // gpio >> 5
    int num         = SunXI::GPIO_NUM(pin);  // gpio & 0x1F

    LOGTRACE("%s gpio(%d) bank(%d) num(%d)", __PRETTY_FUNCTION__, pin, bank, num)
    volatile SunXI::sunxi_gpio_t *pio = &(pio_map->gpio_bank[bank]);
    /* DK, for PL and PM */
    if (bank >= 11) {
        bank -= 11;
        pio = &(r_pio_map->gpio_bank[bank]);
    }

    regval = pio->DAT;
    regval = regval >> num;
    regval &= 1;
    return regval;
}

void GPIOBUS_BananaM2p::set_pullupdn(int pin, int pud)
{
    GPIO_FUNCTION_TRACE
    int clk_offset = SunXI::PULLUPDNCLK_OFFSET + (pin / 32);
    int shift      = (pin % 32);

#ifdef BPI
    if (bpi_found == 1) {
        gpio = *(pinTobcm_BP + pin);
        return sunxi_set_pullupdn(pin, pud);
    }
#endif
    if (pud == SunXI::PUD_DOWN)
        *(gpio_map + SunXI::PULLUPDN_OFFSET) = (*(gpio_map + SunXI::PULLUPDN_OFFSET) & ~3) | SunXI::PUD_DOWN;
    else if (pud == SunXI::PUD_UP)
        *(gpio_map + SunXI::PULLUPDN_OFFSET) = (*(gpio_map + SunXI::PULLUPDN_OFFSET) & ~3) | SunXI::PUD_UP;
    else // pud == PUD_OFF
        *(gpio_map + SunXI::PULLUPDN_OFFSET) &= ~3;

    SunXI::short_wait();
    *(gpio_map + clk_offset) = 1 << shift;
    SunXI::short_wait();
    *(gpio_map + SunXI::PULLUPDN_OFFSET) &= ~3;
    *(gpio_map + clk_offset) = 0;
}
