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

#include "r_ssp_sci.h"
#include "r_sci_spi.h"
#include "r_sci_spi_cfg.h"
#include <cslr.h>
#include <OsTime.h>
#include <OsProcess.h>



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

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

///////////////////////////////////////////////////////////////////////////
#include <trace_probes.h>
#ifndef trace_ssp_sci_irq
trace_need(ssp_sci_irq)
// on during sci operation starting
trace_need(ssp_sci_start)
#endif
#ifndef trace_ssp_sci_hung
trace_need(ssp_sci_hung)
#endif


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

/** @brief if defined - drops additinal checks of actual transfer status.
///         treats that sci spi driver gives relyable TRANSFER_COMPLETE event
///
/// @note  current FSP driver implementation unrelyable for high speed vs slow DTC
 *          it accasionaly got TXI iqr, and comes to finish transaction.
 *
 *          this bug solved in local r_sci_spi implementation, so thin one can declare R_SCISPI_SAFE
 *
 */
#ifndef R_SCISPI_SAFE
#define R_SCISPI_SAFE 0
#endif



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

PTResult    ssp_sci_aquire_dummy(struct SSP_IOPort* this, unsigned cs_id);
DevResult   ssp_sci_release_dummy(struct SSP_IOPort* this);

DevResult ssp_sci_trx(SSP_IOPort* self, SSPMessage* msg);
PTResult  ssp_sci_wait(struct SSP_IOPort* self, SSPMessage* msg, unsigned to /*= toInfinite*/);
DevResult ssp_sci_abort(struct SSP_IOPort* self, SSPMessage* msg);
void ssp_sci_isr_handle(spi_callback_args_t *ev);



DevResult ssp_sci_init(SSP_SCIPort* this, sci_spi_instance_ctrl_t* sci, const spi_cfg_t* spi_cfg){

    fsp_err_t fspok = R_SCI_SPI_Open(sci, spi_cfg);
    if (fspok != FSP_SUCCESS){
        // retry after explicit port close
        R_SCI_SPI_Close(sci);

        fspok = R_SCI_SPI_Open(sci, spi_cfg);
        ASSERT_SSP(fspok == FSP_SUCCESS);

        if (fspok != FSP_SUCCESS)
            return fspok;
    }

    this->ctrl      = sci;
    this->regs      = sci->p_reg;

    const transfer_instance_t * p_transfer = sci->p_cfg->p_transfer_tx;
    this->tx_dma    = p_transfer->p_cfg->p_info;

    p_transfer = sci->p_cfg->p_transfer_rx;
    this->rx_dma    = p_transfer->p_cfg->p_info;

    this->spi.aquire    = ssp_sci_aquire_dummy;
    this->spi.release   = ssp_sci_release_dummy;
    this->spi.trx       = ssp_sci_trx;
    this->spi.wait      = ssp_sci_wait;
    this->spi.abort     = ssp_sci_abort;
    this->spi.msg_signal= NULL;

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

    R_SCI_SPI_CallbackSet(sci, (ssp_sci_isr_handle), this, NULL);

    return DEV_OK;
}

DevResult ssp_sci_close(SSP_SCIPort* this){
    if (this->ctrl != NULL)
        return R_SCI_SPI_Close(this->ctrl);
    return DEV_OK;
}


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

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


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

static inline
bool sci_is_enabled(SSP_SCIPort* this){
    /* sci_spi Disables receiver, transmitter by SCK down. */
    enum {
        //UART_ENABLES = (USART_IT_TX | USART_IT_TXE| USART_IT_RX),
        UART_ENABLES = (USART_TE | USART_RE),
    };
    return (this->regs->SCR & UART_ENABLES) != 0;
}

static DevResult ssp_sci_prepare(struct SSP_SCIPort* this, SSPMessage* msg);

static /*inline*/
DevResult ssp_sci_start(SSP_SCIPort* this, void* dst, const void* src, uint16_t len){

    trace_ssp_sci_start_on();
    fsp_err_t ok = R_SCI_SPI_WriteRead(this->ctrl, src, dst, len, this->ssp_bw);
    trace_ssp_sci_start_off();

    if (ok == FSP_SUCCESS){
        if (sci_is_enabled(this))
            return DEV_BUSY;
        // looks it alredy finished
        this->msg = NULL;
        return DEV_OK;
    }

    ASSERT_SSP(ok == 0);
    // все коды FSP > 0 - ошибки, чтобы они не пересеклись с кодами DevResult, поменяю им знак
    return -ok;
}


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

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

    this->msg_status = SCISPI_STATE_HEAD;
    this->msg = msg;

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

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

            this->msg_status = SCISPI_STATE_HEAD;

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

            this->msg_status = SCISPI_STATE_BODY;

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

    if ( ok == DEV_BUSY)
    if (this->bytes_tick > 0) {
        // startup timeout
        this->start_time = clock_now();
        this->least_bytes = this->ctrl->count;
    }

    return ok;
}

// ожидает завершения транзакции msg.
// TODO: ssp_sci_wait(.. ,to)  - не реализована поддержка to
PTResult ssp_sci_wait(struct SSP_IOPort* self, SSPMessage* msg, unsigned to /*= toInfinite*/){
    (void)msg; (void)to;
    SSP_SCIPort* this = (SSP_SCIPort*) self;

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

    //if  ( sci_is_enabled(this) )
    //    return ptWAITING;

    if (this->msg_status > SCISPI_STATE_DONE){
        if (this->bytes_tick <= 0)
            return ptWAITING;

        os_time_t hrnow = clock_now();
        int passes = hrnow - this->start_time;
        if (passes <= 1)
            return ptWAITING;

        this->start_time = hrnow;

        spi_master_status_t  io_status = {0,0};

        /*ok =*/ R_SCI_SPI_StatusGet(this->ctrl, &io_status);

        if (io_status.pended > 0){

            // check that some bytes was transfered
            if (this->least_bytes > io_status.pended) {
                this->least_bytes = io_status.pended;
                return ptWAITING;

            }

            trace_ssp_sci_hung_twist();

            // hunged tranmition, retry it continue
            PTResult ok = R_SCI_SPI_Continue(this->ctrl);

            if (ok == FSP_SUCCESS)
                return ptWAITING;

            else {
                this->msg_status = SCISPI_STATE_BREAK;
                return ptNOK;
            }
        }
    }

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

    if (this->msg_status == SCISPI_STATE_DONE)
        return ptOK;
    else
        return ptNOK;
}

// обывает транзакцию
DevResult ssp_sci_abort(struct SSP_IOPort* self, SSPMessage* msg){
    (void)msg;
    SSP_SCIPort* this = (SSP_SCIPort*) self;
    ASSERT_SSP(this->ctrl != NULL);

    sci_spi_instance_ctrl_t*    ctrl = this->ctrl;

#if SPI_TRANSMIT_FROM_RXI_ISR
    ctrl->rx_count = ctrl->count;
    ctrl->tx_count = ctrl->count;
    if (this->rx_dma)
        this->rx_dma->length = 0;
    if (this->tx_dma)
        this->tx_dma->length = 0;
#else
    ctrl->tx_count = ctrl->count;
    ctrl->rx_count = ctrl->count;
    if (this->tx_dma)
        this->tx_dma->length = 0;
    if (this->rx_dma)
        this->rx_dma->length = 0;
#endif

    return DEV_OK;
}



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

void ssp_sci_isr_handle(spi_callback_args_t *ev){

    trace_ssp_sci_irq_twist();

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

    if ( ev->event == SPI_EVENT_TRANSFER_COMPLETE){   ///< The data transfer was completed

#ifndef R_SCISPI_SAFE
        // validate actual transfered len
        msg = this->msg;

        int sent;
        sent = msg->word_count - this->ctrl->tx_count;

        // validate actual dma transfered len
        if (msg->src) {
            if (this->tx_dma)
                sent = (uintptr_t)(this->tx_dma->p_src) - (uintptr_t)(msg->src);
        }
        else if (msg->dst) {
            if (this->rx_dma)
                sent = (uintptr_t)(this->rx_dma->p_dest) - (uintptr_t)(msg->dst);
        }

        if ( UNLIKELY(sent != msg->word_count) )
            this->msg_status = SCISPI_STATE_BREAK;

        else
#endif
        switch(this->msg_status){
            case SCISPI_STATE_HEAD:
                msg = this->msg;

                if (msg->word_count > 0){
                    this->msg_status = SCISPI_STATE_BODY;
                    DevResult ok = ssp_sci_start(this, msg->dst, msg->src, msg->word_count);

                    if (ok == DEV_BUSY)
                        {;}
                    else if (ok == DEV_OK)
                        this->msg_status = SCISPI_STATE_DONE;
                    else
                        this->msg_status = SCISPI_STATE_ERROR;
                }
                else
                    this->msg_status = SCISPI_STATE_DONE;

                break;

            case SCISPI_STATE_BODY:
            default:
                this->msg_status = SCISPI_STATE_DONE;
        };
    }
    else if (ev->event == SPI_EVENT_TRANSFER_ABORTED){
        this->msg_status = SCISPI_STATE_ABORT;
    }
    else {
        this->msg_status = SCISPI_STATE_ERROR;
    }

    if (this->msg_status <= SCISPI_STATE_DONE) // finished message transaction
    {
        ssp_sci_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 );
    }
}



///////////////////////////////////////////////////////////////////////////
//-------------------------------------------------------------------------------
static
DevResult ssp_sci_hw_config (struct SSP_SCIPort* 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;

    unsigned NB = CSL_FEXT(mode, SSP_msNB);
    if (NB != 0){
        if (NB  == 8){
            this->ssp_bw = SPI_BIT_WIDTH_8_BITS;
        }
        else
            return SSP_errMODE_NOT_SUPP;
    }

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

    bool is_clkfall = ((mode & SSP_msCPHA) == SSP_msCLKFALL);
    if ( is_clkfall == is_clkhi )
        tmpreg |= USART_PHASE_1EDGE;
    else
        tmpreg |= USART_PHASE_2EDGE;

    USART_TypeDef* io = this->regs;
    ASSERT_SSP(io != NULL);

    unsigned tmpcr = io->SPMR;
    tmpcr &= ~(USART_MODE_Msk);
    tmpreg |= tmpcr;
    io->SPMR = (uint8_t)tmpreg;

    bool need_lsb = ((mode & SSP_msENDIAN) != SSP_msMSB_FIRST);
    bool is_lsb = USART_TransferBitOrderLSB(io) == USART_LSB_FIRST;
    if ( need_lsb != is_lsb ){
        USART_SetTransferBitOrder(io, (need_lsb)? USART_LSB_FIRST : USART_MSB_FIRST );
    }

    return DEV_OK;
}

static
void ssp_sci_baud_config (struct SSP_SCIPort* this, unsigned baud_hz){
    sci_spi_div_setting_t tmp_bdr;
    fsp_err_t ok = R_SCI_SPI_CalculateBitrate(baud_hz, &tmp_bdr, false);
    if (ok == FSP_SUCCESS){
        USART_TypeDef* io = this->regs;
        ASSERT_SSP(io != NULL);
        io->BRR             = tmp_bdr.brr;
        io->SMR_b.CKS       = tmp_bdr.cks;
        this->fsck_hz = baud_hz;
    }
}

static
DevResult ssp_sci_prepare(struct SSP_SCIPort* this, SSPMessage* msg){


    unsigned needmode = msg->mode & (SSP_msMASK | SSP_msNB_Msk);
    if (this->ssp_mode != needmode){
        DevResult ok = ssp_sci_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_sci_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;
}


