/*
 * r_ssp_spi.c
 *
 *  Created on: 3/06/2021
 *      Author: alexrayne <alexraynepe196@gmail.com>
 */

#include "r_ssp_spi.h"
#include "r_spi.h"
#include "r_spi_cfg.h"
#include "r_dtc.h"
#include <cslr.h>
#include <sys/process.h>



///////////////////////////////////////////////////////////////////////////

#if 0
#include <assert.h>
#define ASSERT_SSP(...) assert(__VA_ARGS__)
#else
#define ASSERT_SSP(...)
#endif

///////////////////////////////////////////////////////////////////////////
#include <trace_probes.h>
#ifndef trace_ssp_spi_irq
trace_need(ssp_spi_irq)
// on during spi operation starting
trace_need(ssp_spi_start)
#endif



///////////////////////////////////////////////////////////////////////////

PTResult    ssp_spi_aquire_dummy(struct SSP_IOPort* this, unsigned cs_id){
    (void)this; (void)cs_id;
    return (PTResult)DEV_NOT_IMPLEMENTED;
};

DevResult   ssp_spi_release_dummy(struct SSP_IOPort* this){
    (void)this;
    return DEV_NOT_IMPLEMENTED;
};

DevResult ssp_spi_trx(SSP_IOPort* self, SSPMessage* msg);
PTResult  ssp_spi_wait(struct SSP_IOPort* self, SSPMessage* msg, unsigned to /*= toInfinite*/);
DevResult ssp_spi_abort(struct SSP_IOPort* self, SSPMessage* msg);
void ssp_spi_isr_handle(spi_callback_args_t *ev);



DevResult ssp_spi_init(SSP_SPIPort* this, spi_instance_ctrl_t* spi, const spi_cfg_t* spi_cfg){
    int ok;

    ok = R_SPI_Open(spi, spi_cfg);
    ASSERT_SSP( ok == FSP_SUCCESS);
    if (ok != FSP_SUCCESS)
        return ok;


    this->ctrl      = spi;
    this->regs      = spi->p_regs;

    const transfer_instance_t * p_transfer = spi->p_cfg->p_transfer_tx;
    this->tx_ctl    = p_transfer->p_ctrl;
    this->tx_dma    = p_transfer->p_cfg->p_info;

    p_transfer      = spi->p_cfg->p_transfer_rx;
    this->rx_ctl    = p_transfer->p_ctrl;
    this->rx_dma    = p_transfer->p_cfg->p_info;

    this->spi.aquire    = ssp_spi_aquire_dummy;
    this->spi.release   = ssp_spi_release_dummy;
    this->spi.trx       = ssp_spi_trx;
    this->spi.wait      = ssp_spi_wait;
    this->spi.abort     = ssp_spi_abort;
    this->spi.msg_signal= NULL;

    this->ssp_mode      = ~0ul;
    this->ssp_bw        = (spi_bit_width_t)~0ul;

    R_SPI_CallbackSet(spi, (ssp_spi_isr_handle), this, NULL);

    return DEV_OK;
}


///////////////////////////////////////////////////////////////////////////

static DevResult ssp_spi_prepare(struct SSP_SPIPort* this, SSPMessage* msg);

fsp_err_t ssp_spi_write_read( SSP_SPIPort* this, void* dst, const void* src, uint16_t len);

static /*inline*/
DevResult ssp_spi_start(SSP_SPIPort* this, void* dst, const void* src, uint16_t len){
    //return R_SPI_WriteRead(this->ctrl, src, dst, len, this->ssp_bw);
    fsp_err_t ok = ssp_spi_write_read(this, dst, src, len);
    if(ok == FSP_SUCCESS)
        return DEV_BUSY;
    return -ok;
}


// trx - неблокирующий запуск транзакции. завершение ее
// \return errBUSY - если занято другой транзакцией
//         DEV_BUSY - транзакция в процессе завершения.
//                      можно дождаться через wait
DevResult ssp_spi_trx(SSP_IOPort* self, SSPMessage* msg){
    SSP_SPIPort* this = (SSP_SPIPort*) self;
    if (this->msg_status > SPI_STATE_DONE)
        return DEV_BUSY;

    if (this->msg != NULL)
        return SSP_errBUSY;

    trace_ssp_spi_start_on();

    // запомню процесс запустивший транзакцию - он будет просигнализирован после её исполнения
    ssp_signal_to_me(self);

    this->msg_status = SPI_STATE_HEAD;
    this->msg = msg;

    DevResult ok = ssp_spi_prepare(this, msg);
    if ( ok == DEV_OK){
        if ((msg->head != NULL) && (msg->head_count > 0)){

            this->msg_status = SPI_STATE_HEAD;

            ok = ssp_spi_start(this, NULL, msg->head, msg->head_count);
        }
        else if (msg->word_count > 0){

            this->msg_status = SPI_STATE_BODY;

            ok = ssp_spi_start(this, msg->dst, msg->src, msg->word_count);
        }
    }

    trace_ssp_spi_start_off();

    return ok;
}

// ожидает завершения транзакции msg.
PTResult ssp_spi_wait(struct SSP_IOPort* self, SSPMessage* msg, unsigned to /*= toInfinite*/){
    SSP_SPIPort* this = (SSP_SPIPort*) self;

    if (this->msg == NULL)
        return ptOK;

    if  ( SPI_is_enabled(this->regs) > 0)
        return ptWAITING;

    //if (this->msg_status <= SPI_STATE_DONE)
    //    return ptOK;

    // транзакция завершена, и опрошена -> освобожу порт.
    this->msg = NULL;
    return ptOK;
}

// обывает транзакцию
DevResult ssp_spi_abort(struct SSP_IOPort* self, SSPMessage* msg){
    SSP_SPIPort* this = (SSP_SPIPort*) self;
    ASSERT_SSP(io != NULL);

    spi_instance_ctrl_t*    ctrl = this->ctrl;

#if SPI_TRANSMIT_FROM_RXI_ISR
    ctrl->rx_count = ctrl->count;
    ctrl->tx_count = ctrl->count;
#else
    ctrl->tx_count = ctrl->count;
    ctrl->rx_count = ctrl->count;
#endif

    return DEV_OK;
}



///////////////////////////////////////////////////////////////////////////
static
void ssp_spi_finished(SSP_SPIPort* this){
    if ((this->msg->mode & SSP_msCS_HOLD) == 0){
        this->spi.release(&this->spi);
    }
}

void ssp_spi_isr_handle(spi_callback_args_t *ev){
    trace_ssp_spi_irq_on();

    SSP_SPIPort* this = (SSP_SPIPort*) (ev->p_context);
    SSPMessage* msg;

    if ( ev->event == SPI_EVENT_TRANSFER_COMPLETE){   ///< The data transfer was completed
        switch(this->msg_status){
            case SPI_STATE_HEAD:
                msg = this->msg;

                if (msg->word_count > 0){
                    fsp_err_t ok = ssp_spi_write_read(this, msg->dst, msg->src, msg->word_count);

                    if(ok == FSP_SUCCESS)
                        this->msg_status = SPI_STATE_BODY;
                    else
                        this->msg_status = SPI_STATE_ERROR;
                }
                else
                    this->msg_status = SPI_STATE_DONE;

                break;

            case SPI_STATE_BODY:
            default:
                this->msg_status = SPI_STATE_DONE;
        };
    }
    else if (ev->event == SPI_EVENT_TRANSFER_ABORTED){
        this->msg_status = SPI_STATE_ABORT;
    }
    else {
        this->msg_status = SPI_STATE_ERROR;
    }

    if (this->msg_status <= SPI_STATE_DONE) {// finished message transaction
        ssp_spi_finished(this);
        msg = this->msg;
        if (msg)
        if (msg->on_complete )
            (msg->on_complete)(&this->spi, msg);

        // WARN: can place here since this ssp handles by IRQ
        if (this->spi.msg_signal)
            process_poll( (struct process *)this->spi.msg_signal );
    }

    trace_ssp_spi_irq_off();

}



/*******************************************************************************************************************//**
 * (c)Renesas this code imported from FSP 3.0
 *
 * Copy configured bit width from the current tx data location into the SPI data register.
 * If the transmit buffer is NULL, than write zero to the SPI data register.
 * If the total transfer length has already been transmitted than do nothing.
 *
 * @param[in]  p_ctrl          pointer to control structure.
 **********************************************************************************************************************/
static
void r_spi_transmit (spi_instance_ctrl_t * p_ctrl)
{
    uint32_t tx_count = p_ctrl->tx_count;
    if (tx_count == p_ctrl->count)
    {
        return;
    }

    if (0 == p_ctrl->p_tx_data)
    {
        /* Transmit zero if no tx buffer present. */
        p_ctrl->p_regs->SPDR = 0;
    }
    else
    {
        if (SPI_BIT_WIDTH_8_BITS == p_ctrl->bit_width)
        {
            p_ctrl->p_regs->SPDR_BY = ((uint8_t *) p_ctrl->p_tx_data)[tx_count];;
        }
        else if (SPI_BIT_WIDTH_16_BITS == p_ctrl->bit_width)
        {
            p_ctrl->p_regs->SPDR_HA = ((uint16_t *) p_ctrl->p_tx_data)[tx_count];;
        }
        else                           /* SPI_BIT_WIDTH_32_BITS */
        {
            p_ctrl->p_regs->SPDR = ((uint32_t *) p_ctrl->p_tx_data)[tx_count];;
        }
    }

    p_ctrl->tx_count = tx_count + 1;
}

/*******************************************************************************************************************//**
 * (c)Renesas this code imported from FSP 3.0.
 *
 * Initiates a SPI transfer by setting the SPE bit in SPCR.
 *
 * @param[in]  p_ctrl          pointer to control structure.
 *
 * Note: When not using the DTC to transmit, this function pre-loads the SPI shift-register and shift-register-buffer
 * instead of waiting for the transmit buffer empty interrupt. This is required when transmitting from the
 * Receive Buffer Full interrupt, but it does not interfere with transmitting when using the transmit buffer empty
 * interrupt.
 **********************************************************************************************************************/
static
void ssp_spi_start_transfer (SSP_SPIPort* this, spi_instance_ctrl_t * p_ctrl)
{
#if SPI_TRANSMIT_FROM_RXI_ISR == 1
    if (this->tx_dma == NULL)
    {
        /* Handle the first two transmit empty events here because transmit interrupt may not be enabled. */

        /* Critical section required so that the txi interrupt can be handled here instead of in the ISR. */
        FSP_CRITICAL_SECTION_DEFINE;
        FSP_CRITICAL_SECTION_ENTER;

        /* Enable the SPI Transfer. */
        SPI_Enable(this->regs);

        /* Must call transmit to kick off transfer when transmitting from rxi ISR. */
        r_spi_transmit(p_ctrl);        ///< First data immediately copied into the SPI shift register.

        /* Second transmit significantly improves slave mode performance. */
        r_spi_transmit(p_ctrl);        ///< Second data copied into the SPI transmit buffer.

        /* Must clear the txi IRQ status (The interrupt was handled here). */
        R_BSP_IrqEnable(p_ctrl->p_cfg->txi_irq);

        FSP_CRITICAL_SECTION_EXIT;
    }
    else
    {
        /* Enable the SPI Transfer. */
        SPI_Enable(this->regs);
    }

#else

    /* Enable the SPI Transfer. */
    SPI_Enable(this->regs);
#endif
}

/*******************************************************************************************************************//**
 * (c)Renesas this code imported from FSP 3.0
 *
 * Configures the initiates a SPI transfer for all modes of operation.
 * !!! this code MUST call after R_SPIOpen - that is initalises all other stuff
 *
 * @param      p_src             Buffer to transmit data from.
 * @param      p_dest            Buffer to store received data in.
 * @param[in]  length            Number of transfers
 *
 * @retval     FSP_SUCCESS       Transfer was started successfully.
 * @retval     FSP_ERR_ASSERTION An argument is invalid.
 * @retval     FSP_ERR_NOT_OPEN  The instance has not been initialized.
 * @retval     FSP_ERR_IN_USE    A transfer is already in progress.
 * @return                       See @ref RENESAS_ERROR_CODES for other possible return codes. This function internally
 *                               calls @ref transfer_api_t::reconfigure.
 **********************************************************************************************************************/
//(spi_ctrl_t * const    p_api_ctrl, void const* p_src, void* p_dest, uint32_t length, spi_bit_width_t const bit_width)
fsp_err_t ssp_spi_write_read( SSP_SPIPort* this, void* dst, const void* src, uint16_t len)
{
    spi_instance_ctrl_t * p_ctrl = (spi_instance_ctrl_t *) this->ctrl;

#if SPI_CFG_PARAM_CHECKING_ENABLE
    FSP_ASSERT(NULL != p_ctrl);
    FSP_ERROR_RETURN(SPI_OPEN == p_ctrl->open, FSP_ERR_NOT_OPEN);
    FSP_ASSERT(p_src || p_dest);
    FSP_ASSERT(0 != length);
    if (p_ctrl->p_cfg->p_transfer_tx || p_ctrl->p_cfg->p_transfer_rx)
    {
        FSP_ASSERT(length <= SPI_DTC_MAX_TRANSFER);
    }
#endif

    FSP_ERROR_RETURN(0 == (p_ctrl->p_regs->SPCR & R_SPI0_SPCR_SPE_Msk), FSP_ERR_IN_USE);

    p_ctrl->p_tx_data = src;
    p_ctrl->p_rx_data = dst;
    p_ctrl->tx_count  = 0;
    p_ctrl->rx_count  = 0;
    p_ctrl->count     = len;
    p_ctrl->bit_width = this->ssp_bw;

#if SPI_DTC_SUPPORT_ENABLE == 1
    transfer_info_t *  rx_dma = this->rx_dma;
    if (rx_dma) {
        /* When the rxi interrupt is called, all transfers will be finished. */
        p_ctrl->rx_count = len;

        if (NULL != dst){
            rx_dma->dest_addr_mode = TRANSFER_ADDR_MODE_INCREMENTED;
            rx_dma->p_dest         = dst;
        }
        else  {
            static uint32_t dummy_rx;
            rx_dma->dest_addr_mode = TRANSFER_ADDR_MODE_FIXED;
            rx_dma->p_dest         = &dummy_rx;
        }
        /* Configure the receive DMA instance. */
        //rx_dma->size           = (transfer_size_t) (??);
        rx_dma->length         = len;

        R_DTC_Enable(this->rx_ctl);
        //R_DTC_Reconfigure(this->rx_ctl, rx_dma);
    }

    transfer_info_t *  tx_dma = this->tx_dma;
    if (tx_dma)
    {
        /* When the txi interrupt is called, all transfers will be finished. */
        p_ctrl->tx_count = len;

        /* Configure the transmit DMA instance. */
        if (NULL != src){
            tx_dma->src_addr_mode = TRANSFER_ADDR_MODE_INCREMENTED;
            tx_dma->p_src         = src;
        }
        else {
            static uint32_t dummy_tx = 0;
            tx_dma->src_addr_mode = TRANSFER_ADDR_MODE_FIXED;
            tx_dma->p_src         = &dummy_tx;
        }
        //tx_dma->size          = (transfer_size_t) (??);
        tx_dma->length        = (uint16_t) len;

        R_DTC_Enable(this->tx_ctl);
        //R_DTC_Reconfigure(this->tx_ctl, tx_dma);
    }
#endif

    ssp_spi_start_transfer(this, p_ctrl);
    return FSP_SUCCESS;
}


///////////////////////////////////////////////////////////////////////////
//-------------------------------------------------------------------------------
static
DevResult ssp_spi_hw_config (struct SSP_SPIPort* this, unsigned mode)
{
    const unsigned mode_mask = SSP_msCPOL | SSP_msCPHA | SSP_msENDIAN | SSP_msNB_Msk;
    if ( (this->ssp_mode& mode_mask) == (mode & mode_mask))
        return DEV_OK;

    uint32_t    tmpreg = 0;
    /* SSPx CR0 Configuration */
    bool is_clkhi = ((mode & SSP_msCPOL) == SSP_msCLKHI );
    if (is_clkhi)
        tmpreg |= SPI_POLARITY_HIGH;
    else
        tmpreg |= SPI_POLARITY_LOW;

    bool is_clkfall = ((mode & SSP_msCPHA) == SSP_msCLKFALL);
    if ( is_clkfall == is_clkhi )
        tmpreg |= SPI_PHASE_1EDGE;
    else
        tmpreg |= SPI_PHASE_2EDGE;

    if ((mode & SSP_msENDIAN) != SSP_msMSB_FIRST){
        tmpreg |= SPI_LSB_FIRST;
    }

    SPI_TypeDef* io = this->regs;
    ASSERT_SSP(io != NULL);
    unsigned tmpcr = io->SPCMD[0];
    tmpcr &= ~(SPI_MODE_Msk | SPI_LSB_FIRST );
    tmpreg |= tmpcr;

    unsigned NB = CSL_FEXT(mode, SSP_msNB);
    if (NB != 0){
        transfer_size_t dma_bytes = TRANSFER_SIZE_1_BYTE;

        uint32_t spdcr  = io->SPDCR;

        if (NB  <= 8){
            CSL_FINS(tmpreg, SPI_DATAWIDTH, SPI_DATAWIDTH_8BIT);
            this->ssp_bw    = SPI_BIT_WIDTH_8_BITS;
            //io->SPDCR_b.SPBYT = 1;
            io->SPDCR = spdcr | R_SPI0_SPDCR_SPBYT_Msk;
        }

        else if (NB <= 16) {
            CSL_FINS(tmpreg, SPI_DATAWIDTH, SPI_DATAWIDTH_16BIT);
            this->ssp_bw    = SPI_BIT_WIDTH_16_BITS;
            //io->SPDCR_b.SPBYT = 0;
            //io->SPDCR_b.SPLW  = 0;
            io->SPDCR = spdcr & ~(R_SPI0_SPDCR_SPBYT_Msk | R_SPI0_SPDCR_SPLW_Msk);
            dma_bytes = TRANSFER_SIZE_2_BYTE;
        }

        else if (NB <= 32) {
            CSL_FINS(tmpreg, SPI_DATAWIDTH, SPI_DATAWIDTH_32BIT);
            this->ssp_bw    = SPI_BIT_WIDTH_32_BITS;
            //io->SPDCR_b.SPBYT = 0;
            //io->SPDCR_b.SPLW  = 1;
            io->SPDCR = (spdcr & ~R_SPI0_SPDCR_SPBYT_Msk) | R_SPI0_SPDCR_SPLW_Msk;
            dma_bytes = TRANSFER_SIZE_4_BYTE;
        }

        else
            return SSP_errMODE_NOT_SUPP;

        transfer_info_t *  rx_dma = this->rx_dma;
        if (rx_dma) {
            rx_dma->length = dma_bytes;
        }

        transfer_info_t *  tx_dma = this->tx_dma;
        if (tx_dma) {
            tx_dma->length = dma_bytes;
        }

    }

    SPI_Disable(io);
    io->SPCMD[0] = tmpreg;
    return DEV_OK;
}

static
void ssp_spi_baud_config (struct SSP_SPIPort* this, unsigned baud_hz){
    rspck_div_setting_t tmp_bdr;
    fsp_err_t ok = R_SPI_CalculateBitrate(baud_hz, &tmp_bdr);
    if (ok == FSP_SUCCESS){
        SPI_TypeDef* io = this->regs;
        ASSERT_SSP(io != NULL);
        io->SPCMD_b[0].BRDV = tmp_bdr.brdv;
        io->SPBR            = tmp_bdr.spbr;
        this->fsck_hz = baud_hz;
    }
}

static
DevResult ssp_spi_prepare(struct SSP_SPIPort* this, SSPMessage* msg){

    unsigned needmode = msg->mode & (SSP_msMASK | SSP_msNB_Msk);
    if (this->ssp_mode != needmode){
        DevResult ok = ssp_spi_hw_config(this, msg->mode);
        if (ok != DEV_OK)
            return ok;
    }
    this->ssp_mode = needmode;

    if (msg->freq == SSP_speedKEEP)
    {;}
    else if (msg->freq == SSP_speedMAX)
    {;} // TODO: max speed selection support need, how?
    else if (this->fsck_hz != msg->freq) {
        unsigned flo  = msg->freq - msg->freq/4;
        unsigned fhi  = msg->freq + msg->freq/8;
        if ( (this->fsck_hz < flo ) || (this->fsck_hz > fhi) ) {
            ssp_spi_baud_config(this, msg->freq);
        }
    }

    unsigned CS = CSL_FEXT(msg->mode, SSP_msCS);
    if (CS != SSP_msCS_KEEP){
        PTResult ok = this->spi.aquire(&this->spi, CS);
        return AS_DEVRESULT(ok);
    }
    else
        return DEV_OK;
}



#if 0

/// this code imported from r_spi.c and some improved

/*******************************************************************************************************************//**
 * ISR called when data is loaded into SPI data register from the shift register.
 **********************************************************************************************************************/
static
void spi_rxi_finish (spi_instance_ctrl_t * p_ctrl){
    /* If the transmit and receive ISRs are too slow to keep up at high bitrates,
     * the hardware will generate an interrupt before all of the transfers are completed.
     * By enabling the transfer end ISR here, all of the transfers are guaranteed to be completed. */
    if ((0 == p_ctrl->p_regs->SPSR_b.IDLNF) || (SPI_MODE_SLAVE == p_ctrl->p_cfg->operating_mode))
    {
        R_BSP_IrqStatusClear(p_ctrl->p_cfg->tei_irq);

        /* Disable the SPI Transfer. */
        p_ctrl->p_regs->SPCR_b.SPE = 0;

        /* Signal that a transfer has completed. */
        r_spi_call_callback(p_ctrl, SPI_EVENT_TRANSFER_COMPLETE);
    }
    else
        R_BSP_IrqEnableNoClear(p_ctrl->p_cfg->tei_irq);
}

void spi_rxi_isr (void)
{
    /* Save context if RTOS is used */
    FSP_CONTEXT_SAVE;

    IRQn_Type irq = R_FSP_CurrentIrqGet();
    R_BSP_IrqStatusClear(irq);

    spi_instance_ctrl_t * p_ctrl = (spi_instance_ctrl_t *) R_FSP_IsrContextGet(irq);

    if (p_ctrl->rx_count == p_ctrl->count)
    {
        trace_spi_irq_on();

        spi_rxi_finish(p_ctrl);

        trace_spi_irq_off();

        /* Restore context if RTOS is used */
        FSP_CONTEXT_RESTORE;
        return;
    }

    r_spi_receive(p_ctrl);

#if SPI_TRANSMIT_FROM_RXI_ISR == 1

    /* It is a little faster to handle the transmit buffer empty event in the receive buffer full ISR.
     * Note that this is only possible when the instance is not using a transfer instance to receive data. */
    r_spi_transmit(p_ctrl);
#endif

    if (p_ctrl->rx_count == p_ctrl->count)
    {
        trace_spi_irq_on();

        spi_rxi_finish(p_ctrl);

        trace_spi_irq_off();
    }

    /* Restore context if RTOS is used */
    FSP_CONTEXT_RESTORE;
}

#endif
