/*
 * ssp_hal.cpp
 *
 *  Created on: 15 нояб. 2018 г.
   Copyright 2018 alexrayne <alexraynepe196@gmail.com>

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
 */

#include "ssp_hal.hpp"
#include <cslr.h>
#include <system.h>
#include <c_compat.h>

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

SSP_Client::SSP_Client()
: port(NULL)
{
};


SSPIO_Device::SSPIO_Device()
: SSP_Client()
, adr_style(addr1BYTE)
, adr_mask(0xff)
{
};

//adress part sends as NetOrder - MSB firts
//virtual
int SSPIO_Device::readByte(addr_t address, u8 *data){
    ssp_words[0] = address;
    int sent = ioData(ssp_buf, ssp_buf, (addr_bytes()+1) );
    if (sent <= 0 )
        return DEV_NOK;
    if (data)
        *data = ssp_buf[addr_bytes()];
    return DEV_OK;
}
//virtual
int SSPIO_Device::writeByte(addr_t address, u8 data){
    ssp_words[0] = address;
    ssp_words[addr_bytes()] = data;
    return ioData(NULL, ssp_buf, (addr_bytes()+1) );
}
//virtual
int SSPIO_Device::readData(addr_t address, u8 *data, size_t size){
    // TODO if (size + adress) < ssp_buffer_size optimisation possible
    int ok = send_adress(address);
    if (ok < 0)
        return ok;
    return readData(data, size);
}
//virtual
int SSPIO_Device::writeData(addr_t address, const u8 *data, size_t size){
    // TODO if (size + adress) < ssp_buffer_size optimisation possible
    int ok = send_adress(address);
    if (ok < 0)
        return ok;
    return writeData(data, size);
}
//virtual
int SSPIO_Device::readData(uint8_t cmd, addr_t address, void* data, size_t size){
    int ok;
    if (addr_bytes() < addr4BYTE){
        ok = send_cmdadress(cmd, address);
        if (ok < 0)
            return ok;
        return ioData((u8*)data, NULL, size);
    }
    else {
        ok = ioByte(NULL, cmd, ssp_msg.mode | ssp_t::msCS_HOLD);
        if (ok < 0)
            return ok;
        return readData(address, (u8*)data, size);
    }
}
//virtual
int SSPIO_Device::writeData(uint8_t cmd, addr_t address, const void *data, size_t size){
    int ok;
    if (addr_bytes() < addr4BYTE){
        ok = send_cmdadress(cmd, address);
        if (ok < 0)
            return ok;
        return ioData(NULL, (u8*)data, size);
    }
    else {
        ok = ioByte(NULL, cmd, ssp_msg.mode | ssp_t::msCS_HOLD);
        if (ok < 0)
            return ok;
        return writeData(address, (u8*)data, size);
    }
}

//virtual
int SSPIO_Device::send_cmdadress(uint8_t cmd, addr_t address){
    ssp_words[0] = (msg_addr(address & adr_mask) << 8) | cmd;
    int ok = ioData(NULL, ssp_buf, addr_bytes()+1, ssp_msg.mode | ssp_t::msCS_HOLD);
    return ok;
}

int SSPIO_Device::write_cmdadress4(uint8_t cmd, addr_t address){
    if ((adr_style & addrBE) == 0){
        // Big Endian - MSF first
        address = bswap32(address);
    }
    ssp_words[0] = (address << 8) | cmd;
    ssp_buf[5]   = (address>>24);
    return postData(ssp_buf, 5);
}

int SSPIO_Device::write_cmdadress(uint8_t cmd, addr_t address){
    ssp_words[0] = (msg_addr(address & adr_mask) << 8) | cmd;
    msg_bytes(NULL, ssp_words, addr_bytes()+1);
    return msg_trx();
}

//virtual
int SSPIO_Device::send_adress(addr_t address){
    ssp_words[0] = msg_addr(address & adr_mask);
    int ok = ioData(NULL, ssp_buf, addr_bytes(), ssp_msg.mode | ssp_t::msCS_HOLD);
    return ok;
}

// return adress bytes adjusted by adress style
u32  SSPIO_Device::msg_addr(u32 x){
    if ((adr_style & addrBE) == 0){
        // Big Endian - MSF first
        return bswap32(x) >> (32-addr_bytes()*8);
    }
    else{
        return x;
    }
}


// HAL_IO_Device
//virtual
DevResult SSPIO_Device::readByte(u8 *data){
    return io_ASDEVRESULT(ioByte(data, 0), 1);
}

//virtual
DevResult SSPIO_Device::writeByte(u8 data){
    return io_ASDEVRESULT(ioByte(NULL, data), 1);
}

//virtual
int SSPIO_Device::postByte(u8 data){
    ssp_buf[0] = data;
    // TODO??? должна ли тут быть проверка на то что текущая транзакция занята?
    msg_bytes(NULL, ssp_buf, 1);
    return post_trx();
}

//virtual
int SSPIO_Device::postData(void *dst, const void *data, size_t size){
    // эта проверка по идее лдолжне быть переложена на вызывающее ПО
    PTResult ok = port->wait(ssp_msg);
    if (ok != ptOK)
        return -1;

    msg_bytes(dst, data, size);
    return post_trx();
}

//virtual
int SSPIO_Device::postData(const void *data, size_t size){
    msg_bytes(NULL, data, size);
    return post_trx();
}


SSP_Client::IOResult SSP_Client::post_trx(){
    DevResult ok = port->trx(ssp_msg);
    if (ok == DEV_OK)
        return ssp_msg.word_count;
    if (ok == DEV_BUSY)
        return 0;
    return -1;
}

//virtual
PTResult SSPIO_Device::wait_flush(unsigned to){
    return port->wait(ssp_msg, to);
}


// \return - bytes avail for read

// duplex write-read transfer via SPI
int SSPIO_Device::ioByte(u8 *dst, u8 data){
    ssp_buf[0] = data;
    msg_bytes(dst, ssp_buf, 1);
    return msg_trx();
}

int SSPIO_Device::ioData(void *dst, const void *data, size_t size){
    msg_bytes(dst, data, size);
    return msg_trx();
}

int SSPIO_Device::ioByte(u8 *dst, u8 data, unsigned mode){
    unsigned save_mode = ssp_msg.mode;
    ssp_msg.mode = mode;
    int ok = ioByte(dst, data);
    ssp_msg.mode = save_mode;
    return ok;
}

int SSPIO_Device::ioData(void *dst, const void *data, size_t size, unsigned mode){
    unsigned save_mode = ssp_msg.mode;
    ssp_msg.mode = mode;
    int ok = ioData(dst, data, size);
    ssp_msg.mode = save_mode;
    return ok;
}

//virtual
int SSPIO_Device::readData(u8 *data, size_t size){
    return ioData(data, NULL, size);
}

//virtual
int SSPIO_Device::writeData(const u8 *data, size_t size){
    return ioData(NULL, data, size);
}


SSP_Client::IOResult SSP_Client::msg_trx(){
    DevResult ok = port->trx(ssp_msg);
    trace_sspio_wait_on();
    if (ok == DEV_BUSY){
        PTResult wok = port->wait(ssp_msg);
        // !!!! WARN: блокируюсь здесь до успешного завершения операции
        for (; PT_SCHEDULE(wok); wok = port->wait(ssp_msg))
            wait_fast();
        ok = (wok==ptOK)? DEV_OK:DEV_NOK;
    }
    trace_sspio_wait_off();
    if (ok == DEV_OK)
        return ssp_msg.word_count;
    return -1;
}

