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

#include "ssp_hal.h"
#include "hw.h"
#include "os_system.h"



//////////////////////////////////////////////////////////////////////////////////////
#include <trace_probes.h>
#ifndef trace_sspio_wait
trace_need(sspio_wait)
#endif




//////////////////////////////////////////////////////////////////////////////////////
///                         SSP_IOPort API

#include "OsProcess.h"

void ssp_signal_to_me(SSP_IOPort* this){
    this->msg_signal = PROCESS_CURRENT();
}

void ssp_signal_disable(SSP_IOPort* this){
    this->msg_signal = NULL;
}



//////////////////////////////////////////////////////////////////////////////////////
///                         SSP_IO Client API

PTResult sspio_ready(SSP_Client* this) {
    SSP_IOPort* io = sspio_io(this);
    return io->wait(io, &this->ssp_msg, 0);
};

PTResult sspio_wait(SSP_Client* this, unsigned to /* = HAL_ASYNCIO_Device::toInfinite*/ ){
    SSP_IOPort* io = sspio_io(this);
    return io->wait(io, &this->ssp_msg, to);
};

void sspio_msg_assign_buf(SSP_Client* this, uint16_t words){
    this->ssp_msg.dst = this->ssp_buf;
    this->ssp_msg.src = this->ssp_buf;
    this->ssp_msg.word_count = words;
};


// NON_BLOCKING message post
SSPResult sspio_post_trx(SSP_Client* this){
    SSP_IOPort* io = sspio_io(this);
    return sspio_post_msg(io, &this->ssp_msg);
}

SSPResult   sspio_post_msg(SSP_IOPort* io, SSPMessage* msg)
{
    DevResult ok = io->trx(io, msg);
    if (ok == DEV_OK)
        return msg->word_count;
    if (ok == DEV_BUSY)
        return 0;
    if (ok < 0)
        return ok;
    return DEV_FAIL;
}


SSPResult sspio_msg_trx(SSP_Client* this){
    SSP_IOPort* io = sspio_io(this);
    DevResult ok = io->trx(io, &this->ssp_msg);

    trace_sspio_wait_on();
    if (ok == DEV_BUSY){
        ok = sspio_wait_trx(this);
    }
    trace_sspio_wait_off();

    if (ok == DEV_OK)
        return this->ssp_msg.word_count;
    if (ok < 0)
        return ok;
    return DEV_FAIL;
}


// ждет в цикле на проце  завершения операции
DevResult   sspio_wait_trx(SSP_Client* this){
    SSP_IOPort* io = sspio_io(this);
    return sspio_wait_msg(io, &this->ssp_msg);
}

DevResult   sspio_wait_msg(SSP_IOPort* io, SSPMessage* msg) {

    ssp_signal_disable(io);
    PTResult wok = io->wait(io, msg, 0);
    // !!!! WARN: блокируюсь здесь до успешного завершения операции
    while ( PT_SCHEDULE(wok) ) {
        trace_sspio_wait_on();
        wait_fast();
        wok = io->wait(io, msg, 0);
    }
    trace_sspio_wait_off();

    if (wok==ptOK)
        return DEV_OK;
    return (DevResult)wok; //DEV_NOK;
}



//////////////////////////////////////////////////////////////////////////////////////
///                         SSP_Device API


// функции настройки ssp_msg
inline static
void msg_bytes(SSP_IODevice* this, void *data, const void *src, unsigned words){
    sspio_msg_assign_head(&this->io, NULL, 0);  // cleanup head
    sspio_msg_assign(&this->io, data, src, (uint16_t)words);
};

// функции настройки ssp_msg
inline static
void msg_data_bytes(SSP_IODevice* this, void *data, const void *src, unsigned words){
    sspio_msg_assign(&this->io, data, src, (uint16_t)words);
};

inline static
void msg_head_bytes(SSP_IODevice* this, const void *src, int words){
    sspio_msg_assign_head(&this->io, src, (uint8_t)words);
};

// return adress bytes adjusted by adress style
uint32_t  msg_addr(SSP_IODevice* this, uint32_t x){
    if ((this->adr_style & SSPDEV_addrMSB) == 0){
        // Big Endian - MSB first
        uint32_t tmp = bswap32(x);
        return  tmp >> (32-(this->adr_bytes*8));
    }
    else{
        return x;
    }
}

inline
SSPResult sspdev_trx(SSP_IODevice* this){
    return sspio_msg_trx(&this->io);
}


//----------------------------------------------------------------------------
// duplex blocking write-read transfer via SPI

SSPResult sspdev_ioByte(SSP_IODevice* this, void *dst, uint8_t data){
    this->io.ssp_buf[0] = data;
    msg_bytes(this, dst, this->io.ssp_buf, 1);
    return sspdev_trx(this);
}


SSPResult sspdev_ioData(SSP_IODevice* this, void *dst, const void *data, size_t size){
    msg_bytes(this,dst, data, size);
    return sspdev_trx(this);
}

// makes one transfer with specified mode
SSPResult sspdev_ioBytex(SSP_IODevice* this, void *dst, uint8_t data, unsigned mode){
    unsigned save_mode = this->io.ssp_msg.mode;
    this->io.ssp_msg.mode = mode;
    int ok = sspdev_ioByte(this, dst, data);
    this->io.ssp_msg.mode = save_mode;
    return ok;
}

SSPResult sspdev_ioDatax(SSP_IODevice* this, void *dst, const void *data, size_t size, unsigned mode){
    unsigned save_mode = this->io.ssp_msg.mode;
    this->io.ssp_msg.mode = mode;
    int ok = sspdev_ioData(this, dst, data, size);
    this->io.ssp_msg.mode = save_mode;
    return ok;
}

//----------------------------------------------------------------------------
// !!! this operations holds CS
SSPResult sspdev_send_adress(SSP_IODevice* this, sspdev_addr_t address){
    this->io.ssp_words[0] = msg_addr(this, address & this->adr_mask);
    int ok = sspdev_ioDatax(this, NULL, this->io.ssp_buf, this->adr_bytes
                               , this->io.ssp_msg.mode | SSP_msCS_HOLD);
    return ok;
}

SSPResult sspdev_send_cmdadress(SSP_IODevice* this, uint8_t cmd, sspdev_addr_t address){
    this->io.ssp_words[0] = (msg_addr(this, address & this->adr_mask) << 8) | cmd;
    int ok = sspdev_ioDatax(this, NULL, this->io.ssp_buf, this->adr_bytes+1
                               , this->io.ssp_msg.mode | SSP_msCS_HOLD);
    return ok;
}

SSPResult sspdev_write_cmdadress(SSP_IODevice* this, uint8_t cmd, sspdev_addr_t address){
    this->io.ssp_words[0] = (msg_addr(this, address & this->adr_mask) << 8) | cmd;
    msg_bytes(this, NULL, this->io.ssp_words, this->adr_bytes+1);
    return sspdev_trx(this);
}

// send 4byte address
SSPResult sspdev_write_cmdadress4(SSP_IODevice* this, uint8_t cmd, sspdev_addr_t address){
    if ((this->adr_style & SSPDEV_addrBE) == 0){
        // Big Endian - MSF first
        address = bswap32(address);
    }
    this->io.ssp_words[0] = (address << 8) | cmd;
    this->io.ssp_buf[5]   = (uint8_t)(address>>24);
    msg_bytes(this, NULL, this->io.ssp_buf, 5);
    return sspdev_trx(this);
}



//----------------------------------------------------------------------------
//return <0 - some error, >=0 - transfered data amount
//adress part sends as NetOrder - MSB firts
DevResult sspdev_readByte(SSP_IODevice* this, sspdev_addr_t address, void *data){
    this->io.ssp_words[0] = msg_addr(this, address & this->adr_mask);
    int sent = sspdev_ioData(this, this->io.ssp_buf, this->io.ssp_buf, (this->adr_bytes+1) );
    if (sent <= 0 )
        return DEV_NOK;
    if (data)
        *((uint8_t*)data) = this->io.ssp_buf[this->adr_bytes];
    return DEV_OK;
}

DevResult sspdev_writeByte(SSP_IODevice* this, sspdev_addr_t address, uint8_t data){
    this->io.ssp_words[0] = msg_addr(this, address & this->adr_mask);
    this->io.ssp_words[this->adr_bytes] = data;
    return sspdev_ioData(this, NULL, this->io.ssp_buf, (this->adr_bytes+1) );
}

SSPResult sspdev_readData(SSP_IODevice* this, sspdev_addr_t address, void *data, size_t size){
    // TODO if (size + adress) < ssp_buffer_size optimisation possible
    this->io.ssp_words[0] = msg_addr(this, address & this->adr_mask);
    msg_head_bytes(this, &this->io.ssp_words, this->adr_bytes);
    msg_data_bytes(this, data, NULL, size);

    return sspdev_trx(this);
}
SSPResult sspdev_writeData(SSP_IODevice* this, sspdev_addr_t address, const void *data, size_t size){
    // TODO if (size + adress) < ssp_buffer_size optimisation possible
    this->io.ssp_words[0] = msg_addr(this, address & this->adr_mask);
    msg_head_bytes(this, &this->io.ssp_words, this->adr_bytes);
    msg_data_bytes(this, NULL, data, size);

    return sspdev_trx(this);
}


SSPResult sspdev_askData(SSP_IODevice* this, sspdev_addr_t address, void *data, size_t size){
    // TODO if (size + adress) < ssp_buffer_size optimisation possible
    this->io.ssp_words[0] = msg_addr(this, address & this->adr_mask);
    msg_head_bytes(this, &this->io.ssp_words, this->adr_bytes);
    msg_data_bytes(this, data, NULL, size);

    return sspio_post_trx(&this->io);
}

SSPResult sspdev_postData(SSP_IODevice* this, sspdev_addr_t address, const void *data, size_t size){
    // TODO if (size + adress) < ssp_buffer_size optimisation possible
    this->io.ssp_words[0] = msg_addr(this, address & this->adr_mask);
    msg_head_bytes(this, &this->io.ssp_words, this->adr_bytes);
    msg_data_bytes(this, NULL, data, size);

    return sspio_post_trx(&this->io);
}

//----------------------------------------------------------------------------
static
void msg_cmd(SSP_IODevice* this, uint8_t cmd, sspdev_addr_t address){
    if (this->adr_bytes < SSPDEV_addr4BYTE){
        this->io.ssp_words[0] = (msg_addr(this, address & this->adr_mask) << 8) | cmd;
    } else {
        if ((this->adr_style & SSPDEV_addrBE) == 0){
            // Big Endian - MSF first
            address = bswap32(address);
        }
        this->io.ssp_words[0] = (address << 8) | cmd;
        this->io.ssp_buf[5]   = (uint8_t)(address>>24);
    }
}

SSPResult sspdev_readCmdByte(SSP_IODevice* this, uint8_t cmd, sspdev_addr_t address, void *data){
    msg_cmd(this, cmd, address);
    int sent = sspdev_ioData(this, this->io.ssp_buf, this->io.ssp_buf, (this->adr_bytes+2) );
    if (sent <= 0 )
        return DEV_NOK;
    if (data)
        *((uint8_t*)data) = this->io.ssp_buf[this->adr_bytes+1];
    return DEV_OK;
}

SSPResult sspdev_writeCmdByte(SSP_IODevice* this, uint8_t cmd, sspdev_addr_t address, uint8_t data){
    msg_cmd(this, cmd, address);
    this->io.ssp_words[this->adr_bytes+1] = data;
    return sspdev_ioData(this, NULL, this->io.ssp_buf, (this->adr_bytes+2) );
}

SSPResult sspdev_readCmdData(SSP_IODevice* this, uint8_t cmd, sspdev_addr_t address, void *data, size_t size){
    msg_cmd(this, cmd, address);

    msg_head_bytes(this, &this->io.ssp_words, this->adr_bytes+1);
    msg_data_bytes(this, data, NULL, size);

    return sspdev_trx(this);
}

SSPResult sspdev_writeCmdData(SSP_IODevice* this, uint8_t cmd, sspdev_addr_t address, const void *data, size_t size){
    msg_cmd(this, cmd, address);

    msg_head_bytes(this, &this->io.ssp_words, this->adr_bytes+1);
    msg_data_bytes(this, NULL, data, size);

    return sspdev_trx(this);
}

SSPResult sspdev_askCmdData(SSP_IODevice* this, uint8_t cmd, sspdev_addr_t address, void *data, size_t size){
    msg_cmd(this, cmd, address);

    msg_head_bytes(this, &this->io.ssp_words, this->adr_bytes+1);
    msg_data_bytes(this, data, NULL, size);

    return sspio_post_trx(&this->io);
}

SSPResult sspdev_postCmdData(SSP_IODevice* this, uint8_t cmd, sspdev_addr_t address, const void *data, size_t size){
    msg_cmd(this, cmd, address);

    msg_head_bytes(this, &this->io.ssp_words, this->adr_bytes+1);
    msg_data_bytes(this, NULL, data, size);

    return sspio_post_trx(&this->io);
}

/// @brief try access device, and nothing more - 0 bytes read/write
SSPResult sspdev_touch(SSP_IODevice* this){
    msg_head_bytes(this, NULL, 0);
    msg_data_bytes(this, NULL, NULL, 0);

    return sspio_post_trx(&this->io);
}
