/*
 * em_vango_v93xx.c
 *
 *  Created on: 23/07/2021
 *      Author: alexraynepe196@gmail.com
  ------------------------------------------------------------------------
    Copyright (c) alexrayne

   All rights reserved.
   Redistribution and use in source and binary forms, with or without
   modification, are permitted provided that the following conditions are met:
   - Redistributions of source code must retain the above copyright
     notice, this list of conditions and the following disclaimer.
   - Redistributions in binary form must reproduce the above copyright
     notice, this list of conditions and the following disclaimer in the
     documentation and/or other materials provided with the distribution.
   - Neither the name of ARM nor the names of its contributors may be used
     to endorse or promote products derived from this software without
     specific prior written permission.
   *
   THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
   AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
   IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
   ARE DISCLAIMED. IN NO EVENT SHALL COPYRIGHT HOLDERS AND CONTRIBUTORS BE
   LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
   CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
   SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
   INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
   CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
   ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
   POSSIBILITY OF SUCH DAMAGE. *
  ------------------------------------------------------------------------
      VangoTech Energy-meter AFE V93xx chips SPI API
      @sa http://www.vangotech.com/int/product.php?areas=0&bigs=10&smalls=15&id=36
 */

#include "em_vango_v93xx.h"
#include "ptx.h"
// provide: wait_fast
#include "os_system.h"
#include <project-conf.h>
// provide : process_poll
#include "OsProcess.h"

#if V93XX_API_SYNC
#include "sys/rtimer.h"
#endif


/////////////////////////////////////////////////////////////////////////////////
#if 1
#include <assert.h>
#define ASSERT(...) assert(__VA_ARGS__)
#else
#define ASSERT(...)
#endif



//////////////////////////////////////////////////////////////////////////////////////////////////////////////
#include <trace_probes.h>

#ifndef trace_v93xx_isr
trace_need(v93xx_isr);
trace_need(v93xx_wr);
trace_need(v93xx_rd);
trace_need(v93xx_to);
trace_need(v93xx_wait);
trace_need(v93xx_ver);
trace_need(v93xx_retry);
#endif




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

/// @brief 1st spi access timeout after reset
///         ru: таймаут на первый доступ регистрам после ресета
#ifndef V93XX_SPI_1ST_ACCESS_TOMS
//  experimentaly found that after 180ms, spi ack well
#define V93XX_SPI_1ST_ACCESS_TOMS   180
#endif

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

#pragma pack(push, 1)

enum {
    V93HEAD_CMD         = 1,
    V93HEAD_CMD_RD      = 1,
    V93HEAD_CMD_WR      = 0,
};

union V93XX_SPIMsg{
    struct {
        union {
            struct{
                uint8_t rd  :1; // 0 - write, 1 - read
                uint8_t adr :7; // register adress
            };
            uint8_t head;
        };
        uint32_t    data;
        uint8_t     crc;        //< = 0x33 + ~(CMD + Data Byte 0 + Data Byte 1 + Data Byte 2 + DataByte 3 )
    };
    uint8_t     raw[6];
};
#pragma pack(pop)

typedef union V93XX_SPIMsg V93XX_SPIMsg;

uint8_t v93xx_frame_crc( const void* data ){
    V93XX_SPIMsg* frame = (V93XX_SPIMsg*)data;
    return  0x33 + ~(frame->raw[0] + frame->raw[1] + frame->raw[2] + frame->raw[3]+ frame->raw[4]);
}

bool is_v93xx_frame_ok( const void* msg ){
    V93XX_SPIMsg* frame = (V93XX_SPIMsg*)msg;
    return v93xx_frame_crc(msg) == frame->crc;
}



///////////////////////////////////////////////////////////////////////////////////
///         internal API

/// @return mask of verifyable on write bits of register id
uint32_t    v93xx_reg_mask_ver(V93XXAdr id);



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

#include "sys/process.h"

void v93xx_signal_to_me(EMDev_V93XXSpi* x){
    x->iotask.msg_signal = PROCESS_CURRENT();
}

void v93xx_signal_disable(EMDev_V93XXSpi* x){
    x->iotask.msg_signal = NULL;
}



#if V93XX_API_SYNC

// this invokes from ISR, for timeout 50us after SPI operation
static
void v93xx_start_timeout_50us(EMDev_V93XXSpi* x){
    x->iotask.last50us = hrtime_now();
}

static
bool v93xx_is_timeout(EMDev_V93XXSpi* x){
    return (hrtime_now() - x->iotask.last50us) >= HRTIME_US(50u);
}


#define v93xx_ask1(x, data, adr)    v93xx_rd1(x, data, adr)
#define v93xx_iosend1(x, adr, data) v93xx_send1(x, adr, data)


#else


PTResult v93xx_ask1(EMDev_V93XXSpi* x, uint32_t*   data, V93XXAdr adr);
/// @brief plain write to device, without wrie verify
PTResult v93xx_iosend1(EMDev_V93XXSpi* x, V93XXAdr adr, uint32_t data);

#endif

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

void v93xx_init(EMDev_V93XXSpi* x){
    v93xx_reset_on(x);
    PT_INIT( &(x->iotask.pt) );
    x->iotask.addr = NULL;
    x->iotask.optask = V93XXIO_NONE;
    x->iotask.ok     = ptOK;
    memset(&x->stats, 0, sizeof(x->stats) );

    // io operations claims v93xx_is_timeout, so start it first, to reach timeout
    v93xx_start_timeout_50us(x);
}

void v93xx_on_ssp_event(struct SSP_IOPort* self, struct SSPMessage* msg){
    EMDev_V93XXSpi* this = (EMDev_V93XXSpi*)( (uint8_t*)msg - OFFSETOF(EMDev_V93XXSpi, ssp.io.ssp_msg) );

    v93xx_start_timeout_50us(this);
}

void v93xx_on_ssp_rd_event(struct SSP_IOPort* self, struct SSPMessage* msg){
    EMDev_V93XXSpi* this = (EMDev_V93XXSpi*)( (uint8_t*)msg - OFFSETOF(EMDev_V93XXSpi, iotask.rd_msg) );

    v93xx_start_timeout_50us(this);
}


void v93xx_connect(EMDev_V93XXSpi* this, SSP_IOPort*    _port){
    enum {
        sspio_mode_v93xx_8bit = SSP_msCLKLO | SSP_msCLKRISE // SSP_msCLKFALL//
                                | SSP_msNB8
                                | SSP_msCS1 ,
    };
    sspio_connect(&this->ssp.io, _port, sspio_mode_v93xx_8bit, SSP_speedKEEP);

    this->ssp.io.ssp_msg.on_complete = v93xx_on_ssp_event;
    this->iotask.rd_msg.on_complete  = v93xx_on_ssp_rd_event;

    // все операции обмиена будут вестись из iotask.cmdcache -> ssp.io.ssp_buf

    // это классические сообщение записи.
#if 1
    sspio_msg_assign_head(&this->ssp.io, NULL, 0);  // cleanup head
    sspio_msg_assign(&this->ssp.io, this->ssp.io.ssp_buf, &this->iotask.cmdcache,  sizeof(V93XX_SPIMsg) );
#else
    sspio_msg_assign_head(&this->ssp.io, &this->iotask.cmdcache.raw[0], 1);  // cleanup head
    sspio_msg_assign(&this->ssp.io, this->ssp.io.ssp_buf, &this->iotask.cmdcache.raw[1],  sizeof(V93XX_SPIMsg)-1 );
#endif

    // это сообщение чтения с раздельным заголовком и телом. разделение нужно чтобы добавить паузу между командой
    //      и читаемым телом, для доступа к данным внутреннему автомату чипа
    /* FIXED: при инициализации чипа выяснилось что нередко он безответен, до тех пор пока не снизить скорость обмена
           ниже 20к. этот трюк должен помоч работать с любой скоростью обмена <= SCLK/4
    */
    sspmsg_assign_head(&this->iotask.rd_msg, &this->iotask.cmdcache.raw[0], 1);
    sspmsg_assign(&this->iotask.rd_msg, &this->ssp.io.ssp_buf[1], &this->iotask.cmdcache.raw[1],  sizeof(V93XX_SPIMsg)-1 );
}


//< ожидание завершения текущей операции для доступа к девайсу
PTResult v93xx_wait(EMDev_V93XXSpi* this){
    trace_v93xx_wait_twist();

    if (this->iotask.pt.lc == 0)
    //if (this->iotask.optask == V93XXIO_NONE)
        return this->iotask.ok; //ptOK;

    return ptWAITING;
}


//< обрыв выполнения текущей операции
void v93xx_abort(EMDev_V93XXSpi* this){
    this->iotask.addr =NULL;
    this->iotask.data =NULL;
    this->iotask.retry      = -1;
}



#if V93XX_API_SYNC

/// SyncAPI no need waiting io operations, since it blocking
#define v93xx_io_wait(this)     ptOK

#else

__WEAK
void v93xx_notify_dummy(EMDev_V93XXSpi* this){
    ; // process_post(PROCESS_BROADCAST, , this);
}

void on_v93xx_notify(EMDev_V93XXSpi* this)
    __WEAK_REF("v93xx_notify_dummy");

static inline
PTResult v93xx_io_wait(EMDev_V93XXSpi* this){
    return this->iotask.ok;
}

#endif





enum {
    //< timeout after reset for full online
    V93XX_RESET_STARTUP_US  = 2150u,
    // 1st datashit claims 500us - and it works. later they claims 2ms.
    //  so use 3ms for sure
    V93XX_RESET_TIME_US     = 3000u,
    V93XX_RESET_RETRY_LIMIT = 3,
    V93XX_RESET_TIMEOUT_MS  = 10u,

    // время после reset на первую попытку инициация SPI
    V93XX_SPI_TIMEOUT1_MS   = V93XX_SPI_1ST_ACCESS_TOMS,
    // если инициация SPI не прошел, делаю переповтор
    V93XX_SPI_TIMEOUT_MS    = 10,

    V93XX_SPI_RETRY_LIMIT   = 10,

    V93XX_SPI_KEY           = 0x5A7896B4,
    V93XX_RD_RETRY          = 3,
    V93XX_WR_RETRY          = 3,
};

//< последовательность сброса и активация SPI интерфейса
PTResult v93xx_reset(EMDev_V93XXSpi* this){
    struct pt* pt = &(this->iotask.pt);
    PTResult ok;
    os_timeout_t* to = &this->to;

    PT_BEGIN(pt);

    v93xx_signal_to_me(this);
    this->iotask.retry  = 0;

    // try reset V93XX_RESET_RETRY_LIMIT times
    for ( this->iotask.retry_wr = 0
            ; (this->iotask.retry >= 0) && (this->iotask.retry_wr < V93XX_RESET_RETRY_LIMIT)
            ; ++(this->iotask.retry_wr)
            )
    {

        ostimeout_init(to, CLOCKMS(V93XX_RESET_TIMEOUT_MS) );
        PT_WAIT_WHILE( pt, ostimeout_waiting(to) );

    // generate reset pulse
    v93xx_reset_on(this);
    this->io_state = V93XXIO_OFFLINE;

#if V93XX_API_SYNC

    // TODO: cant delay sub-clock reset pulse
    ostimeout_init(to, CLOCKMS_MORE(V93XX_RESET_TIME_US/1000u) );
    PT_WAIT_WHILE( pt, ostimeout_waiting(to) );
    v93xx_reset_off(this);

#else
    this->iotask.to50us = V93XX_RESET_TIME_US/50u +1;
    this->iotask.addr = NULL;
    this->iotask.optask = V93XXIO_RESET;
    v93xx_start_timeout_50us(this);

    //  wait reset and timeout pass
    PT_WAIT_WHILE(pt, this->iotask.to50us != 0 );
    //v93xx_reset_off();
#endif

    if ( V93XX_SPI_TIMEOUT1_MS > 0) {
        ostimeout_init(to, CLOCKMS(V93XX_SPI_TIMEOUT1_MS) );
        PT_WAIT_WHILE( pt, ostimeout_waiting(to) );
    }

    // try establish SPI connection V93XX_SPI_RETRY_LIMIT times
    for (this->iotask.retry  = 0
            ; (this->iotask.retry >= 0) && (this->iotask.retry < V93XX_SPI_RETRY_LIMIT)
            ; ++(this->iotask.retry)
            )
    {

    // negotiate SPI comm
    ok = v93xx_iosend1(this, V93XX_VERSION, V93XX_SPI_KEY);
    if (PT_SCHEDULE(ok)){
        PT_SCHEDULE_RESULT_WHILE(  pt, ok, v93xx_io_wait(this) );
    }

    if (ok == ptOK){
        ok = v93xx_ask1(this, &this->hw_ver, 0x7f);
        if (PT_SCHEDULE(ok)){
            PT_SCHEDULE_RESULT_WHILE(  pt, ok, v93xx_io_wait(this) );
        }
    }

    if (this->iotask.ok == ptOK){
        on_v93xx_notify(this);
        PT_RESULT(pt, ptOK);
    }

    //try again

    ok = v93xx_iosend1(this, V93XX_VERSION, V93XX_SPI_KEY);
    if (PT_SCHEDULE(ok)){
        PT_SCHEDULE_RESULT_WHILE(  pt, ok, v93xx_io_wait(this) );
    }

    if (ok == ptOK){
        ok = v93xx_ask1(this, &this->hw_ver, 0x7f);
        if (PT_SCHEDULE(ok)){
            PT_SCHEDULE_RESULT_WHILE(  pt, ok, v93xx_io_wait(this) );
        }
    }

    if ( (this->iotask.ok == ptOK) ){
        on_v93xx_notify(this);
        PT_RESULT(pt, ptOK);
    }

    if ((this->iotask.retry >= 0) && (this->iotask.retry < V93XX_SPI_RETRY_LIMIT)) {
    ostimeout_init(to, CLOCKMS(V93XX_SPI_TIMEOUT_MS) );
    PT_WAIT_WHILE( pt, ostimeout_waiting(to) );
    }

    }// for (this->iotask.retry

    /*
    if (this->iotask.retry < V93XX_RESET_RETRY_LIMIT) {
        ostimeout_init(to, CLOCKMS(V93XX_RESET_TIMEOUT_MS) );
        PT_WAIT_WHILE( pt, ostimeout_waiting(to) );
    }
    */

    }//for ( this->iotask.retry_wr = V93XX_RESET_RETRY_LIMIT

    on_v93xx_notify(this);
    PT_RESULT(pt, ptNOK);
    PT_END(pt);
}





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

#if !V93XX_API_SYNC

__WEAK
void v93xx_finished_poll(EMDev_V93XXSpi* this){
    if (this->iotask.msg_signal)
        process_poll( (struct process *)this->iotask.msg_signal );
}

void v93xx_finished(EMDev_V93XXSpi* this)
    __WEAK_REF("v93xx_finished_poll");

#endif


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

static
SSPResult v93xx_io_msg(EMDev_V93XXSpi* this){

#if 1
    while ( !v93xx_is_timeout(this) ){
        // FIX: wait_fast() WFE conflicts with RA2L suspending powerdown
        __NOP();//wait_fast();
    }
#endif

#if V93XX_API_SYNC == 0
    SSPResult ok = sspio_post_trx(&this->ssp.io);

#else
    SSPResult ok = sspio_msg_trx(&this->ssp.io);
    v93xx_start_timeout_50us(this);
    trace_v93xx_wr_off();
    trace_v93xx_rd_off();
#endif
    // ssp signaler have setup at request start
    ssp_signal_disable(this->ssp.io.port);

    return ok;
}

static
SSPResult v93xx_io_rdmsg(EMDev_V93XXSpi* this){

#if 1
    while ( !v93xx_is_timeout(this) ){
        // FIX: wait_fast() WFE conflicts with RA2L suspending powerdown
        __NOP();//wait_fast();
    }
#endif

    SSPMessage* msg = &this->iotask.rd_msg;

    msg->mode = this->ssp.io.ssp_msg.mode;
    msg->freq = this->ssp.io.ssp_msg.freq;

    SSPResult ok = sspio_post_msg( sspio_io(&this->ssp.io), msg);

#if V93XX_API_SYNC != 0
    if ( ok == 0 ){
        ok = sspio_wait_msg(sspio_io(&this->ssp.io), msg);
        if (ok == DEV_OK)
            ok = sizeof(V93XX_SPIMsg);
    }

    v93xx_start_timeout_50us(this);
    trace_v93xx_rd_off();
#endif

    // ssp signaler have setup at request start
    ssp_signal_disable(this->ssp.io.port);

    return ok;
}



static inline
V93XX_SPIMsg* v93xx_msg_req(EMDev_V93XXSpi* this){
    return (V93XX_SPIMsg*)this->iotask.cmdcache.raw;
}

static inline
V93XX_SPIMsg* v93xx_msg_ack(EMDev_V93XXSpi* this){
    return (V93XX_SPIMsg*)this->ssp.io.ssp_buf;
}


/// @brief builds rd command on v93xx_msg_cache. and send it
/// @result received ack in v93xx_msg_io
/// @note: cmdcache.data preserved
static
SSPResult v93xx_rd_msg(EMDev_V93XXSpi* this, V93XXAdr adr){
    V93XX_SPIMsg* msg = v93xx_msg_req(this); //x->ssp.io.ssp_buf;
    msg->adr = adr;
    msg->rd  = 1;
    //msg->data = 0;
    msg->crc  = v93xx_frame_crc(msg);

    trace_v93xx_rd_on();
    ++(this->stats.rd);
    this->iotask.ok     = ptWAITING;
    this->iotask.sspok = v93xx_io_rdmsg(this);
    return this->iotask.sspok;
}

/// @brief builds wr command on v93xx_msg_cache. and send it
/// @result received ack in v93xx_msg_io
static
SSPResult v93xx_wr_msg(EMDev_V93XXSpi* this, V93XXAdr adr, uint32_t data){
    V93XX_SPIMsg* msg = v93xx_msg_req(this); //x->ssp.io.ssp_buf;
    msg->adr = adr;
    msg->rd  = 0;
    msg->data = data;
    msg->crc  = v93xx_frame_crc(msg);

    trace_v93xx_wr_on();
    ++(this->stats.wr);
    this->iotask.ok     = ptWAITING;
    this->iotask.sspok  = v93xx_io_msg(this);
    return this->iotask.sspok;
}



#if !V93XX_API_SYNC

//////////////////////////////////////////////////////////////////////////////////////////////////
///             Async API

//<
PTResult v93xx_ask(EMDev_V93XXSpi* x, uint32_t*   data, CV93XXAdrs seq);
PTResult v93xx_ask1(EMDev_V93XXSpi* x, uint32_t*   data, V93XXAdr adr);

/// @brief writes data to registers, verifyed writen content
PTResult v93xx_post(EMDev_V93XXSpi* x, CV93XXAdrs seq, const uint32_t* data);
PTResult v93xx_post1(EMDev_V93XXSpi* x, V93XXAdr adr, uint32_t data);

/// @brief plain write to device, without wrie verify
PTResult v93xx_iosend(EMDev_V93XXSpi* x, CV93XXAdrs seq, const uint32_t* data);
PTResult v93xx_iosend1(EMDev_V93XXSpi* x, V93XXAdr adr, uint32_t data);



//<
PTResult v93xx_rd(EMDev_V93XXSpi* this, uint32_t*   data, CV93XXAdrs seq){
    struct pt* pt = &(this->iotask.pt);
    PTResult ok;

    PT_BEGIN(pt);

    ok = v93xx_ask( this, data, seq);
    if ( !PT_SCHEDULE(ok) )
        PT_RESULT(pt, ok);

    PT_WAIT_WHILE( pt, PT_SCHEDULE( v93xx_io_wait(this) ) );

    on_v93xx_notify(this);
    PT_RESULT(pt, this->iotask.ok);

    PT_END(pt);
}

PTResult v93xx_rd1(EMDev_V93XXSpi* this, uint32_t*   data, V93XXAdr adr){
    struct pt* pt = &(this->iotask.pt);
    PTResult ok;

    PT_BEGIN(pt);

    ok = v93xx_ask1( this, data, adr);
    if ( !PT_SCHEDULE(ok) )
        PT_RESULT(pt, ok);

    PT_WAIT_WHILE( pt, PT_SCHEDULE( v93xx_io_wait(this) ) );

    on_v93xx_notify(this);
    PT_RESULT(pt, this->iotask.ok);

    PT_END(pt);
}

/// @brief writes data to registers, verifyed writen content
PTResult v93xx_wr(EMDev_V93XXSpi* this, CV93XXAdrs seq, const uint32_t* data){
    struct pt* pt = &(this->iotask.pt);
    PTResult ok;

    PT_BEGIN(pt);

    ok = v93xx_post( this, seq, data);
    if ( !PT_SCHEDULE(ok) )
        PT_RESULT(pt, ok);

    PT_WAIT_WHILE( pt, PT_SCHEDULE( v93xx_io_wait(this) ) );
    on_v93xx_notify(this);

    PT_RESULT(pt, this->iotask.ok);

    PT_END(pt);
}

PTResult v93xx_wr1(EMDev_V93XXSpi* this, V93XXAdr adr, uint32_t data){
    struct pt* pt = &(this->iotask.pt);
    PTResult ok;

    PT_BEGIN(pt);

    ok = v93xx_post1( this, adr, data);
    if ( !PT_SCHEDULE(ok) )
        PT_RESULT(pt, ok);

    PT_WAIT_WHILE( pt, PT_SCHEDULE( v93xx_io_wait(this) ) );

    on_v93xx_notify(this);
    PT_RESULT(pt, this->iotask.ok);

    PT_END(pt);
}

/// @brief plain write to device, without wrie verify
PTResult v93xx_send(EMDev_V93XXSpi* this, CV93XXAdrs seq, const uint32_t* data){
    struct pt* pt = &(this->iotask.pt);
    PTResult ok;

    PT_BEGIN(pt);

    ok = v93xx_iosend( this, seq, data);
    if ( !PT_SCHEDULE(ok) )
        PT_RESULT(pt, ok);

    PT_WAIT_WHILE( pt, PT_SCHEDULE( v93xx_io_wait(this) ) );

    on_v93xx_notify(this);
    PT_RESULT(pt, this->iotask.ok);

    PT_END(pt);
}

PTResult v93xx_send1(EMDev_V93XXSpi* this, V93XXAdr adr, uint32_t data){
    struct pt* pt = &(this->iotask.pt);
    PTResult ok;

    PT_BEGIN(pt);

    ok = v93xx_iosend1( this, adr, data);
    if ( !PT_SCHEDULE(ok) )
        PT_RESULT(pt, ok);

    PT_WAIT_WHILE( pt, PT_SCHEDULE( v93xx_io_wait(this) ) );

    on_v93xx_notify(this);
    PT_RESULT(pt, this->iotask.ok);

    PT_END(pt);
}



/**
 * read automat:
 *
 *        +-> read          -> v93xx
 *        |-- !ok?
 *
 */

static
PTResult v93xx_on_retry_rd(EMDev_V93XXSpi* x){

    if (x->iotask.sspok != sizeof(V93XX_SPIMsg) ){
        if ( UNLIKELY((x->iotask.sspok < 0) )
            return x->iotask.sspok;

        ++x->stats.rd_fails;
        return ptNOK;
    }

    //* restore sent cmd byte
    x->ssp.io.ssp_buf[0] = x->iotask.cmdcache.raw[0];

    if (is_v93xx_frame_ok(x->ssp.io.ssp_words) )
        return ptOK;

    if (x->iotask.retry_rd > 0 ) {

        trace_v93xx_retry_twist();

        --x->iotask.retry_rd;
        ++x->stats.rd_retry;

        // снова отправлю команду cmdcache
        x->iotask.sspok = v93xx_io_rdmsg(x);
        if (x->iotask.sspok == 0)
            return ptWAITING;

        if (x->iotask.sspok == sizeof(V93XX_SPIMsg) ) {
            // transmit completes, check after timeout
            v93xx_start_timeout_50us(x);
            return ptWAITING;
        }

    }
    return ptNOK;
}

// read sequence x->iotask.addr by v93xx_on_retry_rd
PTResult v93xx_on_iord(EMDev_V93XXSpi* x){

    while (true) {
        PTResult ok = v93xx_on_retry_rd(x);

        if (PT_SCHEDULE(ok))
            return ok;

        if (ok == ptOK){
            x->iotask.retry_rd = V93XX_RD_RETRY;

            V93XX_SPIMsg* ack = (V93XX_SPIMsg*)x->ssp.io.ssp_buf;
            *(x->iotask.data)++ = ack->data;

            V93XXAdr adr = 0x80;
            if (x->iotask.addr != NULL){
                ++(x->iotask.addr);
                adr = *(x->iotask.addr);
            }

            if (adr < 0x80) {
                SSPResult sspok = v93xx_rd_msg(x, adr);
                if (sspok == 0)
                    return ptWAITING;

                if (sspok == sizeof(V93XX_SPIMsg) ) {
                    // transmit completes, check after timeout
                    v93xx_start_timeout_50us(x);
                    return ptWAITING;
                }
            }
        }
        else {
            ++x->stats.rd_fails;
        }
        x->iotask.ok = ok;
        return ok;
    } // while
}


// single write
// @return ptWAITING - started new write
PTResult v93xx_on_iowr(EMDev_V93XXSpi* x){

    //ASSERT(x->iotask.data != NULL);

    if (x->iotask.addr == NULL){
        x->iotask.ok = ptOK;
        return ptOK;
    }

    ++(x->iotask.addr);
    V93XXAdr adr = *(x->iotask.addr);
    if(adr >= 0x80){
        x->iotask.ok = ptOK;
        return ptOK;
    }

    ++(x->iotask.data);
    SSPResult ok = v93xx_wr_msg(x, adr, *(x->iotask.data));
    if (ok == 0)
        return ptWAITING;

    if (ok == sizeof(V93XX_SPIMsg) ) {
        // transmit completes, check after timeout
        v93xx_start_timeout_50us(x);
        return ptWAITING;
    }

    ++(x->stats.wr_fails);
    x->iotask.ok = ptNOK;
    return ptNOK;
}

/**
 * write retry automat:
 *
 *        +-> write          -> v93xx
 *        |   !ok?      -> NOK
 *        |   ok?       ->              timeout 50us
 *        |                                 |
 *        |   | <-- V93XXIO_VER_WR0 ?  <----|
 *        +-- !ok?                          |
 *            ok?   -> read     -> 93xxx    |
 *                      |                   |
 *                      |                   |
 *              <-- V93XXIO_VER_WR  ?  <----|
 *      v93xx_on_ver_wr
 *            |
 *      v93xx_on_retry_rd
 *          !ok?    -> V93XXIO_VER_WR0
 *          ok?     ->
 */
static
PTResult v93xx_on_retry_wr(EMDev_V93XXSpi* x){

    V93XX_SPIMsg* msg = (V93XX_SPIMsg*)x->iotask.cmdcache.raw; //x->ssp.io.ssp_buf;

    while (x->iotask.retry_wr > 0 ) {
        trace_v93xx_retry_twist();

        --x->iotask.retry_wr;

        // prep write command
        msg->rd = 0;
        msg->crc  = v93xx_frame_crc(msg);

        ++(x->stats.wr_retry);
        // снова отправлю команду cmdcache
        x->iotask.sspok = v93xx_io_msg(x);
        if (x->iotask.sspok == 0)
            return ptWAITING;

        if (x->iotask.sspok == sizeof(V93XX_SPIMsg) ) {
            // transmit completes, check after timeout
            v93xx_start_timeout_50us(x);
            return ptWAITING;
        }
    }
    ++(x->stats.wr_fails);
    return ptNOK;
}

static
PTResult v93xx_on_ver_wr(EMDev_V93XXSpi* x){

    PTResult ok = v93xx_on_retry_rd(x);

    if (PT_SCHEDULE(ok))
        return ok;

    trace_v93xx_ver_twist();

    if (ok != ptOK) {
        ++(x->stats.wr_fails);
        return ok;
    }

    V93XX_SPIMsg* msg = (V93XX_SPIMsg*)x->iotask.cmdcache.raw; //x->ssp.io.ssp_buf;
    V93XX_SPIMsg* ack = (V93XX_SPIMsg*)x->ssp.io.ssp_buf;

    uint32_t    wr = v93xx_reg_mask_ver(msg->adr);
    if (((msg->data ^ ack->data) & wr) == 0) {
        // ok writen, go next data
        return ptOK;
    }

    // not mutch data
    x->iotask.optask = V93XXIO_VER_WR0;

    return v93xx_on_retry_wr(x);
}

static
PTResult v93xx_on_io_verwr(EMDev_V93XXSpi* x){

    PTResult ok = v93xx_on_iowr(x);

    if (ok == ptWAITING){
        x->iotask.optask = V93XXIO_VER_WR0;
        x->iotask.retry_wr = V93XX_WR_RETRY;
    }

    return ok;

}


//< application timeout ISR for SPI io timeout should invoke this handle
void v93xx_on_timeout(EMDev_V93XXSpi* x){
    trace_v93xx_to_off();
    trace_v93xx_isr_on();
    trace_v93xx_rd_off();
    trace_v93xx_wr_off();

    PTResult ok;
    SSP_IOPort* io = x->ssp.io.port;

    if (x->iotask.optask >= V93XXIO_RD)
    {
        SSPMessage* msg = &x->ssp.io.ssp_msg;
        if ((x->iotask.cmdcache.raw[0] & V93HEAD_CMD) == V93HEAD_CMD_RD){
            msg = &x->iotask.rd_msg;
        }

        ok = io->wait(io, msg, 0);
        if (ok == ptOK){
            x->iotask.sspok = sizeof(V93XX_SPIMsg); // x->ssp.io.msg.word_count
        }
    }

    switch (x->iotask.optask){

        case V93XXIO_NONE:
            if (x->iotask.to50us > 0){
                --x->iotask.to50us;
            }
            if (x->iotask.to50us > 0) {
                trace_v93xx_to_on();
                v93xx_start_timeout_50us(x);
                x->iotask.ok = ptWAITING;
            }
            else{
                v93xx_finished(x);
                x->iotask.ok = ptOK;
            }
            break;

        case V93XXIO_RESET:
            if (x->iotask.to50us > 0){
                --x->iotask.to50us;
            }
            if (x->iotask.to50us == 0) {
                v93xx_reset_off(x);
                x->iotask.optask = V93XXIO_NONE;
                x->iotask.to50us = V93XX_RESET_STARTUP_US/50u +1;
            }
            trace_v93xx_to_on();
            v93xx_start_timeout_50us(x);
            x->iotask.ok = ptWAITING;
            break;

        case V93XXIO_RD:
            x->iotask.ok = v93xx_on_iord(x);
            if( !PT_SCHEDULE(x->iotask.ok) )
                v93xx_finished(x);
            break;

        case V93XXIO_WR:
            x->iotask.ok = v93xx_on_iowr(x);
            if( !PT_SCHEDULE(x->iotask.ok) )
                v93xx_finished(x);
            break;


        case V93XXIO_VER_WR0:

            if (x->iotask.sspok != sizeof(V93XX_SPIMsg) ){
                // write op breaken, try as can
                ok = v93xx_on_retry_wr(x);
                if ( !PT_SCHEDULE(ok) )
                    v93xx_finished(x);
                break;
            }

            {
                // prep rd cmdfor sent wr one
                V93XX_SPIMsg* msg = (V93XX_SPIMsg*)x->iotask.cmdcache.raw; //x->ssp.io.ssp_buf;
                msg->rd = 1;
                msg->crc  = v93xx_frame_crc(msg);

                uint32_t ver =  v93xx_reg_mask_ver(msg->adr);
                if (ver == 0) {
                    // регистр только читаемый/стираемый , ненужно верифицировать записанное

                    // send next data
                    x->iotask.retry_wr = V93XX_WR_RETRY;
                    ok = v93xx_on_io_verwr(x);
                    x->iotask.ok = ok;
                    if ( !PT_SCHEDULE(ok) )
                        v93xx_finished(x);
                    break;
                }
            }

            // снова отправлю команду cmdcache
            x->iotask.optask = V93XXIO_VER_WR;
            x->iotask.retry_rd = V93XX_RD_RETRY;

            x->iotask.ok = v93xx_io_rdmsg(x);
            if (x->iotask.ok == SSP_WAIT)
                break;

            trace_v93xx_to_on();
            v93xx_start_timeout_50us(x);
            break;

        case V93XXIO_VER_WR:
            ok = v93xx_on_ver_wr(x);

            if (PT_SCHEDULE(ok))
                break;

            if (ok == ptOK){
                // send next data
                x->iotask.retry_wr = V93XX_WR_RETRY;
                ok = v93xx_on_io_verwr(x);
            }
            x->iotask.ok = ok;
            if ( !PT_SCHEDULE(ok) )
                v93xx_finished(x);
            break;

        default: ;
    }
    trace_v93xx_isr_off();
}



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

static
PTResult v93xx_ioask(EMDev_V93XXSpi* x, V93XXAdr adr){
    ASSERT( adr < 0x80 );
    if (adr >= 0x80)
        return ptOK;

    // hook ssp signaler, since it handles delayed by 50us
    v93xx_signal_to_me(x);

    SSPResult ok = v93xx_rd_msg(x, adr);

    if (ok == 0)
        return ptWAITING;

    if ( UNLIKELY(ok < 0) )
        return ok;

    // операция обмена завершена тут же, запущу таймаут после запроса.
    v93xx_start_timeout_50us(x);
    return ptWAITING;
}

//<
PTResult v93xx_ask(EMDev_V93XXSpi* x, uint32_t*   data, CV93XXAdrs seq){
    if (seq == NULL)
        return ptOK;

    x->iotask.optask    = V93XXIO_RD;
    x->iotask.retry_rd  = V93XX_RD_RETRY;
    x->iotask.data = data;
    x->iotask.addr = seq;

    V93XXAdr adr = *x->iotask.addr;
    return v93xx_ioask(x, adr);
}

PTResult v93xx_ask1(EMDev_V93XXSpi* x, uint32_t*   data, V93XXAdr adr){
    x->iotask.optask    = V93XXIO_RD;
    x->iotask.retry_rd  = V93XX_RD_RETRY;
    x->iotask.data = data;
    x->iotask.addr = NULL;

    return v93xx_ioask(x, adr);
}



static
PTResult v93xx_iopost(EMDev_V93XXSpi* x, V93XXAdr adr, uint32_t data){
    if (adr >= 0x80)
        return ptOK;

    // hook ssp signaler, since it handles delayed by 50us
    v93xx_signal_to_me(x);

    SSPResult ok = v93xx_wr_msg(x, adr, data);

    if (ok == 0)
        return ptWAITING;

    if ( UNLIKELY(ok < 0) )
        return ok;

    // операция обмена завершена тут же, запущу таймаут после запроса.
    v93xx_start_timeout_50us(x);
    return ptWAITING;
}


PTResult v93xx_post1(EMDev_V93XXSpi* x, V93XXAdr adr, uint32_t data){
    x->iotask.optask    = V93XXIO_VER_WR0;
    x->iotask.retry_wr  = V93XX_WR_RETRY;
    x->iotask.data = NULL;
    x->iotask.addr = NULL;

    return v93xx_iopost(x, adr, data );
}

PTResult v93xx_post(EMDev_V93XXSpi* x, CV93XXAdrs seq, const uint32_t* data){
    ASSERT( (seq != NULL) && (data != NULL) );
    if ((seq == NULL) || (data == NULL))
        return ptOK;

    x->iotask.optask    = V93XXIO_VER_WR0;
    x->iotask.retry_wr  = V93XX_WR_RETRY;
    x->iotask.data = (uint32_t*)data;
    x->iotask.addr = seq;

    V93XXAdr adr = *(x->iotask.addr);
    return v93xx_iopost(x, adr, *(x->iotask.data) );
}

/// @brief plain write to device, without wrie verify
PTResult v93xx_iosend(EMDev_V93XXSpi* x, CV93XXAdrs seq, const uint32_t* data){
    ASSERT( (seq != NULL) && (data != NULL) );
    if ((seq == NULL) || (data == NULL))
        return ptOK;

    x->iotask.optask    = V93XXIO_WR;
    x->iotask.data = (uint32_t*)data;
    x->iotask.addr = seq;

    V93XXAdr adr = *(x->iotask.addr);
    return v93xx_iopost(x, adr, *(x->iotask.data) );
}

PTResult v93xx_iosend1(EMDev_V93XXSpi* x, V93XXAdr adr, uint32_t data){
    x->iotask.optask    = V93XXIO_WR;
    x->iotask.data = NULL;
    x->iotask.addr = NULL;

    return v93xx_iopost(x, adr, data );
}

#else //#if V93XX_API_SYNC != 0

/// @note: cmdcache.data preserved
static
V93XXResult v93xx_on_retry_rd(EMDev_V93XXSpi* x, V93XXAdr adr){

  x->iotask.retry_rd = V93XX_RD_RETRY;

  // 1st read builds msg rd(adr)
  SSPResult ok = v93xx_rd_msg(x, adr);

  while (ok >= 0)
  {
    if ( UNLIKELY(ok != sizeof(V93XX_SPIMsg)) ){
        ++x->stats.rd_fails;

        // TODO: retry here?
        return DEV_NOK;
    }

    //* restore sent cmd byte
    x->ssp.io.ssp_buf[0] = x->iotask.cmdcache.raw[0];

    if (is_v93xx_frame_ok(x->ssp.io.ssp_words) )
        return DEV_OK;

    if (x->iotask.retry_rd <= 0 )
        return DEV_NOK;

    trace_v93xx_retry_twist();

    --x->iotask.retry_rd;
    ++x->stats.rd_retry;

    // снова отправлю команду cmdcache
    ok = v93xx_io_rdmsg(x);
  };
  return ok;
}

static
V93XXResult v93xx_ioask(EMDev_V93XXSpi* x, V93XXAdr adr){
    ASSERT( adr < 0x80 );
    if (adr >= 0x80)
        return DEV_OK;

    // hook ssp signaler, since it handles delayed by 50us
    v93xx_signal_to_me(x);

    while (true) {
        // clenup trach in sent msg.data field
        v93xx_msg_req(x)->data = 0;

        V93XXResult ok = v93xx_on_retry_rd(x, adr);

        if (ok == DEV_OK){
            V93XX_SPIMsg* ack = v93xx_msg_ack(x);
            *(x->iotask.data)++ = ack->data;

            if (x->iotask.addr != NULL){
                ++(x->iotask.addr);
                adr = *(x->iotask.addr);
                if (adr < 0x80)
                    continue;
            }
        }
        else {
            ++x->stats.rd_fails;
        }
        x->iotask.ok = ok;
        return ok;

    } // while
}

//<

V93XXResult v93xx_rd(EMDev_V93XXSpi* x, uint32_t*   data, CV93XXAdrs seq){
    if (seq == NULL)
        return ptOK;

    x->iotask.optask    = V93XXIO_RD;
    x->iotask.retry_rd  = V93XX_RD_RETRY;
    x->iotask.data = data;
    x->iotask.addr = seq;

    V93XXAdr adr = *x->iotask.addr;
    return v93xx_ioask(x, adr);
}


V93XXResult v93xx_rd1(EMDev_V93XXSpi* x, uint32_t*   data, V93XXAdr adr){
    x->iotask.optask    = V93XXIO_RD;
    x->iotask.retry_rd  = V93XX_RD_RETRY;
    x->iotask.data = data;
    x->iotask.addr = NULL;

    return v93xx_ioask(x, adr);
}



static
V93XXResult v93xx_ver_wr(EMDev_V93XXSpi* x){

    // prep rd cmdfor sent wr one
    V93XX_SPIMsg* msg = v93xx_msg_req(x); //x->ssp.io.ssp_buf;

    uint32_t ver =  v93xx_reg_mask_ver(msg->adr);
    if (ver == 0)
        // регистр только читаемый/стираемый , ненужно верифицировать записанное
        return DEV_OK;

    trace_v93xx_ver_twist();
    V93XXResult ok = v93xx_on_retry_rd(x, msg->adr);

    if (ok != DEV_OK) {
        ++(x->stats.wr_fails);
        return ok;
    }

    V93XX_SPIMsg* ack = v93xx_msg_ack(x);

    bool same = (((msg->data ^ ack->data) & ver) == 0);

    return (same)? DEV_OK :DEV_NOK;
}

static
V93XXResult v93xx_on_retry_wr(EMDev_V93XXSpi* x, V93XXAdr adr, uint32_t data){

    x->iotask.retry_wr = V93XX_WR_RETRY;
    SSPResult sspok = v93xx_wr_msg(x, adr, data);

    while (sspok > 0 ) {

        if (sspok != sizeof(V93XX_SPIMsg) ) {
            // TODO: retry here?
            break; //return DEV_NOK;
        }

        V93XXResult ok = v93xx_ver_wr(x);
        if (ok == DEV_OK){
            return ok;
        }
        else
            break;


        if (x->iotask.retry_wr <= 0 )
            break;

        trace_v93xx_retry_twist();

        --x->iotask.retry_wr;
        ++(x->stats.wr_retry);

        // prep write command
        V93XX_SPIMsg* msg = v93xx_msg_req(x); //x->ssp.io.ssp_buf;
        msg->rd = 0;
        msg->crc  = v93xx_frame_crc(msg);

        // снова отправлю команду cmdcache
        sspok = v93xx_io_msg(x);
    }

    x->iotask.ok = ptNOK;
    ++(x->stats.wr_fails);
    return DEV_NOK;
}


static
V93XXResult v93xx_iowr(EMDev_V93XXSpi* x, V93XXAdr adr, uint32_t data){
    if (adr >= 0x80)
        return DEV_OK;

    // hook ssp signaler, since it handles delayed by 50us
    v93xx_signal_to_me(x);
    x->iotask.retry_wr = V93XX_WR_RETRY;

    while (true) {

        V93XXResult ok = v93xx_on_retry_wr(x, adr, data);
        if (ok != DEV_OK) {
            x->iotask.ok = ptNOK;
            return ok;
        }

        // send next data

        if (x->iotask.addr == NULL){
            x->iotask.ok = ptOK;
            return DEV_OK;
        }

        ++(x->iotask.addr);
        adr = *(x->iotask.addr);
        if(adr >= 0x80){
            x->iotask.ok = ptOK;
            return DEV_OK;
        }

        ++(x->iotask.data);
        data = *(x->iotask.data);
    }
}

/// @brief writes data to registers, verifyed writen content
V93XXResult v93xx_wr(EMDev_V93XXSpi* x, CV93XXAdrs seq, const uint32_t* data){
    ASSERT( (seq != NULL) && (data != NULL) );
    if ((seq == NULL) || (data == NULL))
        return ptOK;

    x->iotask.optask    = V93XXIO_VER_WR0;
    x->iotask.retry_wr  = V93XX_WR_RETRY;
    x->iotask.data = (uint32_t*)data;
    x->iotask.addr = seq;

    V93XXAdr adr = *(x->iotask.addr);
    return v93xx_iowr(x, adr, *(x->iotask.data) );
}

V93XXResult v93xx_wr1(EMDev_V93XXSpi* x, V93XXAdr adr, uint32_t data){
    x->iotask.optask    = V93XXIO_VER_WR0;
    x->iotask.retry_wr  = V93XX_WR_RETRY;
    x->iotask.data = NULL;
    x->iotask.addr = NULL;

    return v93xx_iowr(x, adr, data );
}



static
V93XXResult v93xx_iosend(EMDev_V93XXSpi* x, V93XXAdr adr, uint32_t data){
    if (adr >= 0x80)
        return DEV_OK;

    // hook ssp signaler, since it handles delayed by 50us
    v93xx_signal_to_me(x);

    while (true) {

        SSPResult ok = v93xx_wr_msg(x, adr, data);
        if (ok != sizeof(V93XX_SPIMsg) ) {
            ++(x->stats.wr_fails);
            x->iotask.ok = ptNOK;
            return DEV_NOK;
        }


        if (x->iotask.addr == NULL){
            x->iotask.ok = ptOK;
            return DEV_OK;
        }

        ++(x->iotask.addr);
        adr = *(x->iotask.addr);
        if(adr >= 0x80){
            x->iotask.ok = ptOK;
            return DEV_OK;
        }

        ++(x->iotask.data);
        data = *(x->iotask.data);
    }
}

/// @brief plain write to device, without wrie verify
V93XXResult v93xx_send(EMDev_V93XXSpi* x, CV93XXAdrs seq, const uint32_t* data){
    ASSERT( (seq != NULL) && (data != NULL) );
    if ((seq == NULL) || (data == NULL))
        return ptOK;

    x->iotask.optask    = V93XXIO_WR;
    x->iotask.data = (uint32_t*)data;
    x->iotask.addr = seq;

    V93XXAdr adr = *(x->iotask.addr);
    return v93xx_iosend(x, adr, *(x->iotask.data) );
}

V93XXResult v93xx_send1(EMDev_V93XXSpi* x, V93XXAdr adr, uint32_t data){
    x->iotask.optask    = V93XXIO_WR;
    x->iotask.data = NULL;
    x->iotask.addr = NULL;

    return v93xx_iosend(x, adr, data );
}



#endif //#if V93XX_API_SYNC != 0



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

struct V93XXRegMask {
    V93XXAdr        adr;
    uint32_t        nwbits;
};

#define V93XX_REG_MASKS(a, nowrite) { .adr = (a), .nwbits = (nowrite) }

const struct V93XXRegMask v93xx_reg_masks[] = {
    V93XX_REG_MASKS(V93XX_SYS_INTSTS    ,  ~0ul ),
    V93XX_REG_MASKS(V93XX_SYS_INTEN     ,  V93XX_SYS_INTEN_RESERVED_Msk ),
    V93XX_REG_MASKS(V93XX_SYS_MISC      ,  ~V93XX_SYS_MISC_WR_Msk ),
    V93XX_REG_MASKS(V93XX_DAT_SWELL_CNT , ~0ul ),
    V93XX_REG_MASKS(V93XX_DAT_DIP_CNT   , ~0ul ),
    V93XX_REG_MASKS(V93XX_DSP_CTRL0     , V93XX_DSP_CTRL0_RESERVED_Msk ),
    V93XX_REG_MASKS(V93XX_DSP_CTRL0     , V93XX_DSP_CTRL1_RESERVED_Msk ),
    V93XX_REG_MASKS(V93XX_DSP_CTRL4     , V93XX_DSP_CTRL4_RESERVED_Msk ),
    V93XX_REG_MASKS(V93XX_DSP_CTRL5     , V93XX_DSP_CTRL5_WAVE_ADDR_CLR | V93XX_DSP_CTRL5_RESERVED_Msk ),

};

/// @return mask of verifyable on write bits of register id
///         it lookups id in v93xx_reg_masks_reg_masks.
///         by default all register is writable
uint32_t    v93xx_reg_mask_ver(V93XXAdr id){
    const struct V93XXRegMask* msk = v93xx_reg_masks;
    for (unsigned cnt = ARRAY_COUNT(v93xx_reg_masks) ;  cnt > 0; ++msk, --cnt) {
        if (msk->adr == id){
            return ~msk->nwbits;
        }
    }
    return ~0ul;
}


/// @brief ищет x в seq, возвращает индекс с головы строки
/// @return -1 - ненайдено
int v93xx_addr_idx(V93XXAdr x, CV93XXAdrs seq){
    for (int i = 0; *seq != V93XX_END; ++i){
        if (*seq++ == x)
            return i;
    }
    return -1;
}
