/*
 * 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:
 *     hspi_slave.c - SPISlave library 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"
#include "CoreUtil.h"


namespace eCxx {

namespace NodeMCU {


//
// Callback function to receive status value
//
static HSPISlave_StatusRxCallback statusRxCallback = nullptr;
static HSPISlave_DataRxCallback     dataRxCallback = nullptr;


//
// Rx data buffer
//
static uint8_t rxDataBuffer[SPI::HSPI_DATA_BUFFER_SIZE];


//
// SPI interrupt vector
//
static void ICACHE_RAM_ATTR spi_isr(void* arg, void* /*frame*/)
{
    const uint32_t istatus = SPIIR;

    // SPI0 ISR
    if( istatus & _BV(SPII0) ) {
        // Clear SPI0 ISR
        SPI0S &= ~(0x3FF);
    }

    // SPI1 ISR
    else if( istatus & _BV(SPII1) ) {
        const uint32_t status = SPI1S;

        SPI1S &= ~0x3E0;   // Disable interrupts
        SPI1S |= SPISSRES; // Reset
        SPI1S &= ~0x01F;   // Clear interrupts
        SPI1S |=  0x3E0;   // Enable interrupts

        if(status & SPISWSIS) {
            if(statusRxCallback) statusRxCallback(arg, SPI1WS);
        }
        else if(status & SPISWBIS) {
            for(uint32_t i = 0; i < 8; ++i) {
                const uint32_t data = SPI1W(i);
                rxDataBuffer[ (i << 2)     ] =  data        & 0xFF;
                rxDataBuffer[ (i << 2) + 1 ] = (data >>  8) & 0xFF;
                rxDataBuffer[ (i << 2) + 2 ] = (data >> 16) & 0xFF;
                rxDataBuffer[ (i << 2) + 3 ] = (data >> 24) & 0xFF;
            }
            if(dataRxCallback) dataRxCallback(arg, rxDataBuffer);
        }
    }
}


//
// HSPI slave low-level functions
//
extern void hspiSlave_begin(uint8_t statusLen, void* arg, HSPISlave_StatusRxCallback srxcb, HSPISlave_DataRxCallback drxcb)
{
    // Set the user callbacks
    if(!srxcb && !drxcb) return;

    statusRxCallback = srxcb;
    dataRxCallback   = drxcb;

    // Ensure the status length is within range
         if(!statusLen    ) statusLen = 1;
    else if( statusLen > 4) statusLen = 4;

    // Set pin mode
    NodeMCU::pinMode(NodeMCU::SPI_SS,   SPECIAL); // GPIO15
    NodeMCU::pinMode(NodeMCU::SPI_MOSI, SPECIAL); // GPIO13
    NodeMCU::pinMode(NodeMCU::SPI_MISO, SPECIAL); // GPIO12
    NodeMCU::pinMode(NodeMCU::SPI_SCK,  SPECIAL); // GPIO14

    // Set configuration in SPI slave register
    // SPISE    : SPI slave enable
    // SPISTRIE : SPI              interrupt enable
    // SPISRSIE : SPI status read  interrupt enable
    // SPISWSIE : SPI status write interrupt enable (probably not needed)
    SPI1S  = SPISE  | SPISTRIE | SPISRSIE | SPISWSIE;

    if(dataRxCallback) {
        // SPISBE   : SPI data buffer enable
        // SPISRBIE : SPI data buffer read  interrupt enable
        // SPISWBIE : SPI data buffer write interrupt enable (probably not needed)
        SPI1S |= (SPISBE | SPISRBIE | SPISWBIE);
    }

    SPI1U   = SPIUMISOH | SPIUCOMMAND | SPIUSSE;
    SPI1U2  = (7 << SPILCOMMAND);
    SPI1S1  = ( ( (statusLen * 8) - 1 ) << SPIS1LSTA )
            | (0xFF << SPIS1LBUF)
            | (7    << SPIS1LWBA)
            | (7    << SPIS1LRBA)
            | SPIS1RSTA;
    SPI1P   = _BV(19);
    SPI1CMD = SPIBUSY;
    SPI1CLK = 0;

    // Makes slave to change MISO value on falling edge on clock signal as is required for SPI mode 1
    SPI1C2 = (0x1 << SPIC2MISODM_S) | (0x2 << SPIC2MOSIDN_S);

    // Enable SPI interrupt
    ETS_SPI_INTR_ATTACH(spi_isr, arg);
    ETS_SPI_INTR_ENABLE();
}


extern void hspiSlave_end()
{
    // Disable SPI interrupt
    ETS_SPI_INTR_DISABLE();
    ETS_SPI_INTR_ATTACH(nullptr, nullptr);

    // Set pin mode
    NodeMCU::pinMode(NodeMCU::SPI_SS,   INPUT); // GPIO15
    NodeMCU::pinMode(NodeMCU::SPI_MOSI, INPUT); // GPIO13
    NodeMCU::pinMode(NodeMCU::SPI_MISO, INPUT); // GPIO12
    NodeMCU::pinMode(NodeMCU::SPI_SCK,  INPUT); // GPIO14

    // Restore to default
    SPI1S  = 0;
    SPI1U  = SPIUSSE | SPIUCOMMAND;
    SPI1S1 = 0;
    SPI1P  = 0b110;

    // Clear the user callback
    statusRxCallback = nullptr;
}


extern void ICACHE_RAM_ATTR hspiSlave_sendStatus(uint32_t status)
{ SPI1WS = status; }


extern void ICACHE_RAM_ATTR hspiSlave_sendData(const uint8_t (&data)[SPI::HSPI_DATA_BUFFER_SIZE])
{
    uint32_t out = 0;
    uint8_t  bi  = 0;
    uint8_t  wi  = 8;

    for(int32_t i = 0; i < SPI::HSPI_DATA_BUFFER_SIZE; ++i) {
        out |= data[i] << ( bi * 8 );
        bi  += 1;
        bi  &= 3;
        if(!bi) {
            SPI1W(wi) = out;
            out       = 0;
            ++wi;
        }
    }
}


} // namespace NodeMCU

} // namespace eCxx
