/*
 * 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 based on:
 *     Microchip TWI/I2C library for Arduino
 *     https://github.com/arduino/ArduinoCore-megaavr
 * ----------------------------------------------------------------------------------
 *
 * Copyright (C) 2018 Microchip Technology Inc. and its subsidiaries.
 *
 * Subject to your compliance with these terms, you may use Microchip software and
 * any derivatives exclusively with Microchip products. It is your responsibility
 * to comply with third party license terms applicable to your use of third party
 * software (including open source software) that may accompany Microchip software.
 *
 * THIS SOFTWARE IS SUPPLIED BY MICROCHIP "AS IS".  NO WARRANTIES, WHETHER EXPRESS,
 * IMPLIED OR STATUTORY, APPLY TO THIS SOFTWARE, INCLUDING ANY IMPLIED WARRANTIES OF
 * NON-INFRINGEMENT, MERCHANTABILITY, AND FITNESS FOR A PARTICULAR PURPOSE. IN NO
 * EVENT WILL MICROCHIP BE LIABLE FOR ANY INDIRECT, SPECIAL, PUNITIVE, INCIDENTAL OR
 * CONSEQUENTIAL LOSS, DAMAGE, COST OR EXPENSE OF ANY KIND WHATSOEVER RELATED TO THE
 * SOFTWARE, HOWEVER CAUSED, EVEN IF MICROCHIP HAS BEEN ADVISED OF THE POSSIBILITY
 * OR THE DAMAGES ARE FORESEEABLE. TO THE FULLEST EXTENT ALLOWED BY LAW, MICROCHIP'S
 * TOTAL LIABILITY ON ALL CLAIMS IN ANY WAY RELATED TO THIS SOFTWARE WILL NOT EXCEED
 * THE AMOUNT OF FEES, IF ANY, THAT YOU HAVE PAID DIRECTLY TO MICROCHIP FOR THIS
 * SOFTWARE.
 */


#include "../AVRCommon/Internal.h"
#include "TWIDriver.h"

#include <eCxx/Platform/AVRX/PinMapping.h>


namespace eCxx {


//
// Transaction result
//
enum TWIM_RESULT {
    TWIM_RESULT_UNKNOWN          = 0x00,
    TWIM_RESULT_OK               = 0x01,
    TWIM_RESULT_BUFFER_OVERFLOW  = 0x02,
    TWIM_RESULT_ARBITRATION_LOST = 0x03,
    TWIM_RESULT_BUS_ERROR        = 0x04,
    TWIM_RESULT_NACK_RECEIVED    = 0x05,
    TWIM_RESULT_FAIL             = 0x06
};


enum TWIS_RESULT {
    TWIS_RESULT_UNKNOWN            = 0x00,
    TWIS_RESULT_OK                 = 0x01,
    TWIS_RESULT_BUFFER_OVERFLOW    = 0x02,
    TWIS_RESULT_TRANSMIT_COLLISION = 0x03,
    TWIS_RESULT_BUS_ERROR          = 0x04,
    TWIS_RESULT_FAIL               = 0x05,
    TWIS_RESULT_ABORTED            = 0x06
};


//
// Allow the ISR code to not be linked when not explicitly used
//
static void (*twi_master_isr_handler)() = nullptr;
static void (*twi_slave_isr_handler )() = nullptr;


//
// A standard AVR TWI driver
//
TWIDriver::TWIDriver()
: _twiMode(TWIMode::Unknown)
{}


void TWIDriver::begin(uint8_t address, SlaveRxCallbackF rxucbSlave, SlaveTxCallbackF txucbSlave, uint32_t frequency)
{
    // No need to continue if the TWI is already enabled
    if(_twiMode != TWIMode::Unknown) return;

    // Master mode
    if(frequency) {
        // Set mode
        _twiMode = TWIMode::Master;
        // Initialize variables
        _twiResult    = TWIM_RESULT_UNKNOWN;
        _transStatus  = false;
        _bytesRead    = 0;
        _bytesWritten = 0;
        // Configure pins
        Application::setPinMode(twiPinSDA, Application::PinMode::InputPullUp);
        Application::setPinMode(twiPinSCL, Application::PinMode::InputPullUp);
        // Set the ISR handler
        twi_master_isr_handler = &TWIDriver::__twi_master_isr_handler;
        // Setup port multiplexer
        PORTMUX.TWISPIROUTEA |= ECXX_TWI_PORT_MUX;
        // Set TWI settings
        masterSetFrequency(frequency);
        TWI0.MCTRLA  = TWI_RIEN_bm | TWI_WIEN_bm | TWI_ENABLE_bm;
        TWI0.MSTATUS = TWI_BUSSTATE_IDLE_gc;
    }

    // Slave mode
    else {
        // Set mode
        _twiMode = TWIMode::Slave;
        // Initialize variables
        _twiResult      = TWIS_RESULT_UNKNOWN;
        _transStatus    = false;
        _bytesRead      = 0;
        _bytesWritten   = 0;
        _slaveCallRXUCB = false;
        _rxucbSlave     = rxucbSlave;
        _txucbSlave     = txucbSlave;
        // Configure pins
        Application::setPinMode(twiPinSDA, Application::PinMode::Input);
        Application::setPinMode(twiPinSCL, Application::PinMode::Input);
        // Set the ISR handler
        twi_slave_isr_handler = &TWIDriver::__twi_slave_isr_handler;
        // Setup port multiplexer
        PORTMUX.TWISPIROUTEA |= ECXX_TWI_PORT_MUX;
        // Set TWI settings
        TWI0.SADDR  = address << 1;
        TWI0.SCTRLA = TWI_DIEN_bm | TWI_APIEN_bm | TWI_PIEN_bm  | TWI_ENABLE_bm;
        TWI0.MCTRLA = TWI_ENABLE_bm;
    }
}


void TWIDriver::end()
{
    // No need to continue if the TWI is currently disabled
    if(_twiMode == TWIMode::Unknown) return;

    // Flush and wait for a while
    TWI0.MCTRLB |= TWI_FLUSH_bm;
    Application::delayMS(10);

    // Set TWI settings
    TWI0.MBAUD   = 0x00;
    TWI0.MCTRLA  = 0x00;
    TWI0.MSTATUS = TWI_BUSSTATE_IDLE_gc;
    TWI0.SADDR   = 0x00;
    TWI0.SCTRLA  = 0x00;

    // Clear the ISR handlers
    twi_master_isr_handler = nullptr;
    twi_slave_isr_handler  = nullptr;

    // Configure pins
    Application::setPinMode(twiPinSDA, Application::PinMode::Input);
    Application::setPinMode(twiPinSCL, Application::PinMode::Input);

    // Reset mode
    _twiMode = TWIMode::Unknown;
}


TWIDriver::TWIResultCode TWIDriver::writeTo(uint8_t address, const uint8_t* data, uint8_t len, bool /*wait*/, bool sendStop)
{
    // Check the mode
    if(_twiMode != TWIMode::Master) return TWIResultCode::OtherError;

    // Check the transaction status
    if(_transStatus) return TWIResultCode::OtherError;

    // Check the length
    if(!len) return TWIResultCode::NoError;

    // Initialize variables
    _twiResult      = TWIM_RESULT_UNKNOWN;
    _transStatus    = true;
    _bytesToWrite   = len;
    _bytesWritten   = 0;
    _rwBuffer       = const_cast<volatile uint8_t*>(data);
    _slaveAddress   = address << 1;
    _masterSendStop = sendStop;

    // Send SLA+W and then send the data
    uint32_t timeoutDelay = Application::CommunicationTimeoutCycles;

    do {
        // Send the address first
        _twiMode   = TWIMode::MasterTransmitter;
        TWI0.MADDR = _slaveAddress & 0b11111110;
        // Wait until all the data are sent
        while(_twiResult == TWIM_RESULT_UNKNOWN) {
            Application::yield();
            // Check for timeout
            if(--timeoutDelay == 0) {
                masterTransactionFinished(TWIM_RESULT_FAIL);
                return TWIResultCode::TimeOut;
            }
        }
    } while(_twiResult == TWIM_RESULT_ARBITRATION_LOST); // Loop to retry in case of arbitration lost

    // Return the result code
    switch(_twiResult) {
        case TWIM_RESULT_OK              : return TWIResultCode::NoError;
        case TWIM_RESULT_BUFFER_OVERFLOW : return TWIResultCode::DataTooLong;
        case TWIM_RESULT_NACK_RECEIVED   : return TWIResultCode::DataNACK;
    }
    return TWIResultCode::OtherError;
}


uint8_t TWIDriver::readFrom(uint8_t address, uint8_t* data, uint8_t len, bool sendStop)
{
    // Check the mode
    if(_twiMode != TWIMode::Master) return 0;

    // Check the transaction status
    if(_transStatus) return 0;

    // Check the length
    if(!len) return 0;

    // Initialize variables
    _twiResult      = TWIM_RESULT_UNKNOWN;
    _transStatus    = true;
    _bytesToRead    = len;
    _bytesRead      = 0;
    _rwBuffer       = data;
    _slaveAddress   = address << 1;
    _masterSendStop = sendStop;

    // Send SLA+R and then receive the data
    uint32_t timeoutDelay = Application::CommunicationTimeoutCycles;

    do {
        // Send the address first
        _twiMode   = TWIMode::MasterReceiver;
        TWI0.MADDR = _slaveAddress | 0b00000001;
        // Wait until all the data are received
        while(_twiResult == TWIM_RESULT_UNKNOWN) {
            Application::yield();
            // Check for timeout
            if(--timeoutDelay == 0) {
                masterTransactionFinished(TWIM_RESULT_FAIL);
                return 0;
            }
        }
    } while(_twiResult == TWIM_RESULT_ARBITRATION_LOST); // Loop to retry in case of arbitration lost

    // Return the number of bytes actually read
    return _bytesRead;
}


TWIDriver::TWIResultCode TWIDriver::transmitToMaster(const uint8_t* data, uint8_t len)
{
    // Check the mode
    if(_twiMode != TWIMode::SlaveTransmitter) return TWIResultCode::NotSlaveTx;

    // Check if there is enough space in the buffer
    if(_txBuffSlave.capacity() < _txBuffSlave.size() + len) return TWIResultCode::DataTooLong;

    // Copy data to buffer
    for(uint8_t i = 0; i < len; ++i) _txBuffSlave.put(*data++);

    // Done
    return TWIResultCode::NoError;
}


//
// Helper functions
//
void TWIDriver::masterSetFrequency(uint32_t frequency)
{
    uint16_t tRise; // See the electrical characteristics section of the datasheet

    if(frequency < 200000) {
        frequency = 100000;
        tRise     = 1000;
    }
    else if(frequency < 800000) {
        frequency = 400000;
        tRise     = 300;
    }
    else if(frequency < 1200000) {
        frequency = 1000000;
        tRise     = 120;
    }
    else {
        frequency = 100000;
        tRise     = 1000;
    }

    constexpr uint32_t cpuFreq = Application::getCPUFrequency();

    const uint32_t baud = ( (cpuFreq / frequency) - ( ( ( cpuFreq * tRise ) / 1000 ) / 1000 ) / 1000 - 10 ) / 2;

    TWI0.MBAUD = static_cast<uint8_t>(baud);
}


void TWIDriver::masterTransactionFinished(uint8_t result)
{
    _twiResult   = result;
    _transStatus = false;
    _twiMode     = TWIMode::Master;
}


void TWIDriver::slaveAddressMatch()
{
    _transStatus = true;
    _twiResult   = TWIS_RESULT_UNKNOWN;

    // Send ACK and then progress to wait for data interrupt
    TWI0.SCTRLB = TWI_SCMD_RESPONSE_gc;

    // Master read - slave write
    if(TWI0.SSTATUS & TWI_DIR_bm) {
        // Set mode first
        _twiMode = TWIMode::SlaveTransmitter;
        // Call the user callback as needed
        if(_txucbSlave) (*_txucbSlave)();
        // Initialize variables
        _bytesToWrite = _txBuffSlave.size();
        _bytesWritten = 0;
    }

    // Master write - slave read
    else {
        _bytesRead      = 0;
        _slaveCallRXUCB = true;
        _twiMode        = TWIMode::SlaveReceiver;
    }
}


void TWIDriver::slaveTransactionFinished(uint8_t result)
{
    TWI0.SCTRLA |= (TWI_APIEN_bm | TWI_PIEN_bm);

    _twiResult   = result;
    _transStatus = false;
    _twiMode     = TWIMode::Slave;

    _txBuffSlave.clear();
}


//
// TWI interrupt handlers
//
void TWIDriver::__twi_master_isr_handler()
{
    const uint8_t currentStatus = TWI0.MSTATUS;

    TWIDriver&    twiDriver     = TWIDriver::instance();

    // Check for bus error or arbitration lost
    if( (currentStatus & TWI_ARBLOST_bm) || (currentStatus & TWI_BUSERR_bm) ) {
        // Check for bus error
        if(currentStatus & TWI_BUSERR_bm) {
            twiDriver._twiResult = TWIM_RESULT_BUS_ERROR;
        }
        // Arbitration lost
        else {
            twiDriver._twiResult = TWIM_RESULT_ARBITRATION_LOST;
        }
        // Clear all flags and abort operation
        TWI0.MSTATUS = currentStatus;
        // Wait for a new operation
        twiDriver._twiMode     = TWIMode::Master;
        twiDriver._transStatus = false;
    }

    // Check for master write interrupt
    else if(currentStatus & TWI_WIF_bm) {
        // Got NACK; cancel the transaction
        if(TWI0.MSTATUS & TWI_RXACK_bm) {
            if(twiDriver._masterSendStop) TWI0.MCTRLB = TWI_MCMD_STOP_gc;
            else                          TWI0.MCTRLB = TWI_MCMD_REPSTART_gc;
            twiDriver.masterTransactionFinished(TWIM_RESULT_NACK_RECEIVED);
        }
        // Send more byte(s)
        else if(twiDriver._bytesWritten < twiDriver._bytesToWrite) {
            TWI0.MDATA = twiDriver._rwBuffer[twiDriver._bytesWritten++];
        }
        // Send SLA+R as needed
        else if(twiDriver._bytesRead < twiDriver._bytesToRead) {
            twiDriver._twiMode = TWIMode::MasterReceiver;
            TWI0.MADDR = twiDriver._slaveAddress | 0b00000001;
        }
        // Transaction completed; send ACK/STOP condition
        else {
            if(twiDriver._masterSendStop) TWI0.MCTRLB = TWI_MCMD_STOP_gc;
            else                          TWI0.MCTRLB = TWI_MCMD_REPSTART_gc;
            twiDriver.masterTransactionFinished(TWIM_RESULT_OK);
        }
    }

    // Check for master read interrupt
    else if(currentStatus & TWI_RIF_bm) {
        // Receive data
        if(twiDriver._bytesRead < twiDriver._bytesToRead) {
            twiDriver._rwBuffer[twiDriver._bytesRead++] = TWI0.MDATA;
        }
        // Buffer overflow; issue NACK/STOP and set a BUFFER_OVERFLOW condition
        else {
            if(twiDriver._masterSendStop) TWI0.MCTRLB = TWI_ACKACT_bm | TWI_MCMD_STOP_gc;
            else                          TWI0.MCTRLB = TWI_ACKACT_bm | TWI_MCMD_REPSTART_gc;
            twiDriver.masterTransactionFinished(TWIM_RESULT_BUFFER_OVERFLOW);
            twiDriver._bytesToRead = 0;
            return;
        }
        // Send ACK to receive more byte(s)
        if(twiDriver._bytesRead < twiDriver._bytesToRead) {
            TWI0.MCTRLB = TWI_MCMD_RECVTRANS_gc;
        }
        // Transaction completed; send ACK/STOP condition
        else {
            if(twiDriver._masterSendStop) TWI0.MCTRLB = TWI_ACKACT_bm | TWI_MCMD_STOP_gc;
            else                          TWI0.MCTRLB = TWI_ACKACT_bm | TWI_MCMD_REPSTART_gc;
            twiDriver.masterTransactionFinished(TWIM_RESULT_OK);
        }
    }

    // Unexpected state
    else {
        twiDriver.masterTransactionFinished(TWIM_RESULT_FAIL);
    }
}


void TWIDriver::__twi_slave_isr_handler()
{
    const uint8_t currentStatus = TWI0.SSTATUS;

    TWIDriver&    twiDriver     = TWIDriver::instance();

    // Check for bus error
    if(currentStatus & TWI_BUSERR_bm) {
        twiDriver._bytesRead    = 0;
        twiDriver._bytesWritten = 0;
        twiDriver._bytesToWrite = 0;
        twiDriver.slaveTransactionFinished(TWIS_RESULT_BUS_ERROR);
    }

    // Check for address match or STOP
    else if(currentStatus & TWI_APIF_bm) {
        // Call the user callback as needed
        if(twiDriver._slaveCallRXUCB) {
            if(twiDriver._rxucbSlave) (*twiDriver._rxucbSlave)(
                twiDriver._rxBuffSlave.data(),
                twiDriver._rxBuffSlave.size()
            );
            twiDriver._slaveCallRXUCB = false;
            twiDriver._rxBuffSlave.clear();
        }
        // Check for address match
        if(currentStatus & TWI_AP_bm) {
            twiDriver.slaveAddressMatch();
        }
        // STOP
        else {
            // Clear APIF; do not ACK or NACK
            TWI0.SSTATUS = TWI_APIF_bm;
            twiDriver.slaveTransactionFinished(TWIS_RESULT_OK);
            // If CLKHOLD is high, we have missed an address match from a fast start after stop;
            // because the flag is shared we need to handle this here
            if(TWI0.SSTATUS & TWI_CLKHOLD_bm) {
                // CLKHOLD will be cleared by servicing the address match
                twiDriver.slaveAddressMatch();
            }
        }
    }

    // Check for data interrupt
    else if(currentStatus & TWI_DIF_bm) {
        // Check for collision
        if(currentStatus & TWI_COLL_bm) {
            twiDriver._bytesRead    = 0;
            twiDriver._bytesWritten = 0;
            twiDriver._bytesToWrite = 0;
            twiDriver.slaveTransactionFinished(TWIS_RESULT_TRANSMIT_COLLISION);
        }
        // Handle normal data interrupt
        else {
            // Enable stop interrupt
            TWI0.SCTRLA |= (TWI_APIEN_bm | TWI_PIEN_bm);
            // Master read - slave Write
            if(TWI0.SSTATUS & TWI_DIR_bm) {
                // Got NACK; slave write transaction finished
                if( (twiDriver._bytesWritten > 0) && (TWI0.SSTATUS & TWI_RXACK_bm) ) {
                    TWI0.SCTRLB = TWI_SCMD_COMPTRANS_gc;
                    twiDriver.slaveTransactionFinished(TWIS_RESULT_OK);
                }
                // Got ACK; master expects more data
                else {
                    // Send byte(s)
                    if(twiDriver._bytesWritten < twiDriver._bytesToWrite) {
                        TWI0.SDATA  = twiDriver._txBuffSlave.data()[twiDriver._bytesWritten++];
                        TWI0.SCTRLB = TWI_SCMD_RESPONSE_gc;
                    }
                    // Buffer overflow
                    else {
                        TWI0.SCTRLB = TWI_SCMD_COMPTRANS_gc;
                        twiDriver.slaveTransactionFinished(TWIS_RESULT_BUFFER_OVERFLOW);
                    }
                }
            }
            // Master write - slave read
            else {
                // Read data
                if( twiDriver._bytesRead < twiDriver._rxBuffSlave.capacity() ){
                    // Receive byte(s)
                    const uint8_t value = TWI0.SDATA;
                    twiDriver._rxBuffSlave.put(value);
                    ++twiDriver._bytesRead;
                    // Send ACK and wait for the next data interrupt
                    TWI0.SCTRLB = TWI_SCMD_RESPONSE_gc;
                }
                // Buffer overflow
                else {
                    TWI0.SCTRLB = TWI_ACKACT_bm | TWI_SCMD_COMPTRANS_gc;
                    twiDriver.slaveTransactionFinished(TWIS_RESULT_BUFFER_OVERFLOW);
                }
            }
        }
    }

    // Unexpected state
    else {
        twiDriver.slaveTransactionFinished(TWIS_RESULT_FAIL);
    }
}


} // namespace eCxx


//
// TWI interrupt vectors
//
ISR(TWI0_TWIM_vect)
{
    if(!eCxx::twi_master_isr_handler) return;

    (*eCxx::twi_master_isr_handler)();
}


ISR(TWI0_TWIS_vect)
{
    if(!eCxx::twi_slave_isr_handler) return;

    (*eCxx::twi_slave_isr_handler)();
}
