/*
 * ssp_hal.h
 *
 *  Created on: 2/06/2021
 *      Author: alexrayne <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. *
 * ------------------------------------------------------------------
 * API SPIdevice  был накидан по мотивам этого драйвера в uOS-embebbed
 *
 *  структура:
 *                                 SSPMessage     +-------------------------+
 *  mcu SPI  <-- SSP_IOPort  <------------------  |SSP_Client: SSPMessage   |
 *    ^           driver                          |            sspbuf       |
 *    |                                           +-------------------------+
 *  addressing                                    |SSP_IODevice: adressing  |
 *   device                                       +-------------------------+
 *                                                | flash ....              |
 *                                                |  ...                    |
 *                                                +-------------------------+
 *  SSP_IODevice - нацелен на реализацию обмена устройств с внутреней адресацией
 *          флешки, и регистровые банки
 *
 */

#ifndef BSP_SSP_HAL_H_
#define BSP_SSP_HAL_H_

#include <c_compat.h>
//provide: NULL
#include <stddef.h>
#include <stdint.h>
#include <stdbool.h>
#include <halos_types.h>
#include <cslr.h>


///////////////////////////////////////////////////////////////////////////////////
///
enum SSPErrorID{
      SSP_WAIT              = 0     ///< operation started and waits
    , SSP_errBUSY           = -6    ///< errBUSY
    , SSP_errMODE_NOT_SUPP  = -7    ///< errMODE_NOT_SUPP
    , SSP_errBAD_CS         = -8    ///< on invalid CS requred
    , SSP_errABORTED        = -9    ///< errABORTED
    , SSP_errNOACK          = -10   /// like SSP_errBAD_CS, but when device not responded
};
// описание конфигурации/режима SPI  для выполнения запроса сообщения
enum SSPModeStyleID{
    // Полярность линии синхроимпульсов. Если флаг не задан, то в режиме ожидания
    // линия переводится в низкий уровень; если задан, то в высокий.
        SSP_msCPOL    = 1u
      , SSP_msCLKHI   = SSP_msCPOL, SSP_msCLKLO   = 0u
    // Выбор фронта синхроимпульса, по которому осуществляется выборка данных.
    // Если CPOL не задан, то при заданном CPHA - по заднему, при незаданном - по
    // переднему. Если CPOL задан, то при заданном CPHA - по переднему, при
    // незаданном - по заднему.
    , SSP_msCPHA    = 2u
    , SSP_msCLKFALL = SSP_msCPHA, SSP_msCLKRISE = 0u
    // Выбор порядка следования бит на линиях данных. Если флаг не задан, то
    // первый передаётся старший бит, иначе младший бит.
    //  !!! При несовпадении этого порядка, с поддерживаемым устройством,
    //      драйвер может попробовать инвертировать порядок в исходных отсылаемых/принимаемых
    //      данных
    , SSP_msENDIAN = 4u
    , SSP_msLSB_FIRST   = SSP_msENDIAN
    , SSP_msMSB_FIRST   = 0u

    // Поведение линии выбора устройства после передачи сообщения. Если флаг не
    // задан, то линия переходит в неактивное состояние автоматически. Иначе
    // остаётся активным до передачи сообщения, в поле mode которого будет
    // отсутствовать этот флаг.
    , SSP_msCS_HOLD     = 8u

    //маска режима SPI
    , SSP_msMASK        = 0xffu

    //селектор CS абонента
    , SSP_msCS_Msk          = 0xff00u, SSP_msCS_Pos = 8u
    , SSP_msCS1         = 1u << SSP_msCS_Pos
    // CS == 0 - использую чтобы снять все активные CS
    , SSP_msCS_NONE     = 0u
    // CS == ~0 - нереальный CS, не управляет линией CS,
    , SSP_msCS_KEEP     = SSP_msCS_Msk

    // количество бит в слове
    , SSP_msNB_Msk          = 0xff0000u, SSP_msNB_Pos = 16u
    , SSP_msNB_KEEP         = 0u
    , SSP_msNB8             = (8u<<SSP_msNB_Pos)
    , SSP_msNB16            = (16u<<SSP_msNB_Pos)


    /// @brief SSP I2C specific flags.
    //  sspdev_touch use empty msg - with no send/recv data.
    //  this flag choose what i2c SLA operation use - RD/WR
    , SSPI2C_TOUCH        = 1u,
    SSPI2C_TOUCH_RD     = 0u,
    SSPI2C_TOUCH_WR     = 1u,

};
static inline unsigned ssp_as_msNB(unsigned bits) {return (bits << SSP_msNB_Pos)& SSP_msNB_Msk;};
static inline unsigned ssp_as_msCS(unsigned cs) {return (cs <<SSP_msCS_Pos)&SSP_msCS_Msk; }
static inline unsigned ssp_msNB(unsigned mode) {return (mode&SSP_msNB_Msk) >> SSP_msNB_Pos;};
static inline unsigned ssp_msCS(unsigned mode) {return (mode&SSP_msCS_Msk) >>SSP_msCS_Pos; }

enum SSPSpeedID{
    // \ value == 0 , not affect speed
      SSP_speedKEEP = 0u
    , SSP_speedMAX  = ~0u
};




struct SSPMessage;
struct SSP_IOPort;

typedef void (* SSPEventHandle )(struct SSP_IOPort* self, struct SSPMessage* msg);

//сообщение передаваемое для запроса обмена драйверу
struct SSPMessage {
    // Количество слов в сообщении.
    uint8_t     head_count;
    uint8_t     dummy;
    //volatile bool  finished;

    uint16_t    word_count;

    // заголовок для отправки перед нагрузкой сообщения, ответные данные игнорируются
    const void*     head;

    // Если поле msg->src равно 0, то в линию должно быть выдано
    //      msg->word_count нулей.
    const void*     src;
    // Если поле msg->dst равно 0, то принятые данные должны быть отброшены.
    void*           dst;

    // Требуемая частота передачи (битовая скорость) \sa SpeedID
    unsigned    freq;
    // Установки для передачи сообщения. \sa StyleID
    unsigned    mode;

    SSPEventHandle   on_complete;
};
typedef struct SSPMessage   SSPMessage;


// функции настройки ssp_msg
static inline
void sspmsg_assign(SSPMessage* this, void *data, const void *src, uint16_t words){
    this->dst = data;
    this->src = src;
    this->word_count = words;
};

// функции настройки ssp_msg
static inline
void sspmsg_assign_head(SSPMessage* this, const void *head, uint8_t words){
    this->head = head;
    this->head_count = words;
};



///////////////////////////////////////////////////////////////////////////////////
///                         SSP_IO API

enum { toInfinite = ~0u};

struct SSP_IOPort {

    // BUS lock access
    PTResult    (*aquire)(struct SSP_IOPort* this, unsigned cs_id);  //{return (PTResult)DEV_NOT_IMPLEMENTED;};
    DevResult   (*release)(struct SSP_IOPort* this);           //{return DEV_NOT_IMPLEMENTED;};


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

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

    // обывает транзакцию
    DevResult (*abort)(struct SSP_IOPort* this, SSPMessage* msg);

    // сигналер сообщения нужен драйверу для извещения о завершении транзакции.
    //  trx при запуске новой транзакции его устанавливает на вызвавший процесс.
    // TODO: жестко связано на contiki - запмнинается нитка запустившая сообщение
    // TODO: стоит это тоже в сообщение переложить?
    void*               msg_signal;
};
typedef struct  SSP_IOPort SSP_IOPort;



// назначить текущую нитку для сигналирования завершения таранзакции
void ssp_signal_to_me(SSP_IOPort* this);

// блокированное ожидание может отключить сигналинг ожидающей нитке, чтобы сэкономить время процессора
void ssp_signal_disable(SSP_IOPort* this);




///////////////////////////////////////////////////////////////////////////////////
///                         SSP_IO Client API
///     Клиет SSP предоставляет SSPMessage для операций на SPI, и
///     небольшой буффер для небольших служебных транзакций - пересылка байта, адресов...

// буффер сообщения для драйвера SSP - тут собираю адресные заголовки для транзакций
enum { ssp_client_buf_limit = 8 ,

        sspio_mode_default_8bit = SSP_msCLKLO | SSP_msCLKRISE
                                | SSP_msNB8
                                | SSP_msCS_KEEP ,

        sspio_mode_default_i2c = SSP_msNB8 | SSP_msCS_KEEP ,
    };

struct SSP_Client {
    SSP_IOPort*     port;

    SSPMessage      ssp_msg;

    union {
        uint8_t     ssp_buf[ssp_client_buf_limit];
        uint16_t    ssp_buf16[ssp_client_buf_limit/2];
        uint32_t    ssp_words[ssp_client_buf_limit/4];
    };
};
typedef struct SSP_Client   SSP_Client;


static inline
void sspio_connect(SSP_Client* this, SSP_IOPort*    _port
        , unsigned mode /* = mode_default_8bit */
        , unsigned freq /* = ssp_t::speedKEEP */
        )
{
    this->port = _port;
    this->ssp_msg.mode = mode;
    this->ssp_msg.freq = freq;
};


//---------------------------------------------------------------------------------

// результаты функций read/write
// \return >0 - len of transfered data
// \return =0 - operation started, and need time. try wait it by is_ready, or  wait
// \return <0  - DevResult
typedef int SSPResult;

// helper - констрейнит результаты функций ioXXXX с заданной длиной
static inline
DevResult sspio_ASDEVRESULT(int x, int oklen) {
    return (x > 0)? ((x==oklen)? DEV_OK : DEV_NOK) : x ;
};

//---------------------------------------------------------------------------------
//          свойства SSP_Client, доступ к полям лучше вести через эти обертки
static inline   SSP_IOPort* sspio_io(SSP_Client* this)  {return this->port;}

static inline   unsigned sspio_mode(SSP_Client* this) {return this->ssp_msg.mode;};
static inline   unsigned sspio_cs(SSP_Client* this) {return CSL_FEXT(this->ssp_msg.mode, SSP_msCS);};
static inline   unsigned sspio_freq(SSP_Client* this) {return this->ssp_msg.freq;};

static inline   void sspio_set_mode(SSP_Client* this, unsigned x) {this->ssp_msg.mode = x;};
static inline   void sspio_set_cs(SSP_Client* this, unsigned id) { CSL_FINS(this->ssp_msg.mode, SSP_msCS, id);};
static inline   void sspio_set_freq(SSP_Client* this, unsigned x) {this->ssp_msg.freq = x;};

//---------------------------------------------------------------------------------
// BUS lock access
// TODO: может надо бы сделать мутех локальный? чтобы допустить конкурентный  доступ к СПИ других чипов?

static inline
PTResult sspio_aquire(SSP_Client* this, unsigned cs_id){
    SSP_IOPort* io = sspio_io(this);
    return io->aquire(io, cs_id);
};

static inline
DevResult sspio_release(SSP_Client* this){
    SSP_IOPort* io = sspio_io(this);
    return io->release(io);
};

// управление удержанием линии после транзакции. Для непосредственного
//  управления селектором используется aquire/release.
//  если надо линию держать несколько транзакций, то планирование удержанием
//    лучше делать через io() - для этого надо указать необходимость удержания
//    линии после исполнения транзакции SSPIO_Device
static inline
void    sspio_cs_hold(SSP_Client* this, bool onoff){
    if (onoff)
        this->ssp_msg.mode |= SSP_msCS_HOLD;
    else
        this->ssp_msg.mode &= (unsigned)~SSP_msCS_HOLD;
};


//---------------------------------------------------------------------------------
// все транзакции read/write могут запускать длительный процесс, который вернет
//  результат c PT_SCHEDULE(x) == true
// протонитки могут полить статус завершения транзакции этим вызовом
PTResult sspio_ready(SSP_Client* this);
PTResult sspio_wait(SSP_Client* this, unsigned to /* = HAL_ASYNCIO_Device::toInfinite*/ );

// функции настройки ssp_msg
static inline
void sspio_msg_assign(SSP_Client* this, void *data, const void *src, uint16_t words){
    this->ssp_msg.dst = data;
    this->ssp_msg.src = src;
    this->ssp_msg.word_count = words;
};

// функции настройки ssp_msg
static inline
void sspio_msg_assign_head(SSP_Client* this, const void *head, uint8_t words){
    this->ssp_msg.head = head;
    this->ssp_msg.head_count = words;
};

void sspio_msg_assign_buf(SSP_Client* this, uint16_t words);

// функции отправки ssp_msg -> io()
// @return  - bytes sent, msg->word_count
SSPResult   sspio_msg_trx(SSP_Client* this);

// неблокирующая отправка ssp_msg
SSPResult   sspio_post_trx(SSP_Client* this);
SSPResult   sspio_post_msg(SSP_IOPort* io, SSPMessage* msg);

// ждет в цикле на проце  завершения операции
// TODO: перейти на SSPResult ?
DevResult   sspio_wait_trx(SSP_Client* this);
DevResult   sspio_wait_msg(SSP_IOPort* io, SSPMessage* msg);




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

//----------------------------------------------------------------------------
// implements CS control, cmd+adress transfers

// Прокси/фильтр дайвер SPI устройств:
//      SSPIO_Device.io() --> SSP_Device
//
// позиционируется как родитель, для реализации произвольного SPI устройства
//  в качестве примера \sa flash_hal.h:SPIFlash_GenDevice
//
// этот фильтр предоставляет готовые медоты АПИ IO/Block_Device для обмена через SSP_Device
// -и помимо этого дает методы обмена фреймами имеющими адресную часть и/или команду:
//  методы: read/writeData(cmd, addr, data)
// - и неблокирующие методы load/sendData/Byte
//
// - формат фрейма можно варьировать - заданием размера адреса, методром style( x )
//   адресная часть может отсылаться прямым (LSB) и обратным (MSB) порядком байт.
//   (обратный порядок более распространен)
//
// - в качетсве простого произвольного универсального обмена даются ioData/Byte() -
//  не-виртуальные методы формирующие запрос к SSP_Device
//
// - методы write/send_cmdadress - отправляют заголовочную часть фрейма - команда+адрес,
//      и оставляют шину занятой для следующихданных
//

// HAL_IO_Block_Device
enum SSP_IODeviceAdrStyleID{
      SSPDEV_addr1BYTE =1 ,
      SSPDEV_addr2BYTE,
      SSPDEV_addr3BYTE,
      SSPDEV_addr4BYTE,
      SSPDEV_addrLE    = 0 ,     // LSB first
      SSPDEV_addrBE    = 0x100 , // MSB first
      SSPDEV_addrMSB   = SSPDEV_addrBE ,
      SSPDEV_addrLSB   = SSPDEV_addrLE ,
};

typedef uint32_t    sspdev_addr_t;
struct SSP_IODevice {
    SSP_Client      io;

    union {
        uint8_t                  adr_bytes;
        unsigned /*AdrStyleID*/  adr_style;
    };
    unsigned    adr_mask;

};
typedef  struct SSP_IODevice SSP_IODevice;


//----------------------------------------------------------------------------
static inline
void sspdev_style(SSP_IODevice* this, unsigned /*AdrStyleID*/ astyle) {
    this->adr_style = astyle;
    this->adr_mask  = (1<<(8*astyle))-1;
};

static inline
unsigned sspdev_addr_bytes(SSP_IODevice* this) {return this->adr_bytes;};


//----------------------------------------------------------------------------
// NON-BLOCKING ops - need sspio_wait or sspdev_wait_flush for wait operation compelete

SSPResult sspdev_askData(SSP_IODevice* this, sspdev_addr_t address, void *data, size_t size);
SSPResult sspdev_postData(SSP_IODevice* this, sspdev_addr_t address, const void *data, size_t size);

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


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


static inline
PTResult  sspdev_wait_flush(SSP_IODevice* this, unsigned to /*= toInfinite*/){
    return sspio_wait(&this->io, to);
}

static inline
DevResult   sspdev_wait_trx(SSP_IODevice* this){
    return sspio_wait_trx(&this->io);
}


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

SSPResult sspdev_ioByte(SSP_IODevice* this, void *dst, uint8_t data);
SSPResult sspdev_ioData(SSP_IODevice* this, void *dst, const void *data, size_t size);

// makes one transfer with specified mode
SSPResult sspdev_ioBytex(SSP_IODevice* this, void *dst, uint8_t data, unsigned mode);
SSPResult sspdev_ioDatax(SSP_IODevice* this, void *dst, const void *data, size_t size, unsigned mode);

static inline
SSPResult sspdev_loadData(SSP_IODevice* this, void *data, size_t size){
    return sspdev_ioData(this, data, NULL, size);
}

static inline
SSPResult sspdev_sendData(SSP_IODevice* this, const void *src, size_t size){
    return sspdev_ioData(this, NULL, src, size);
}

//----------------------------------------------------------------------------
// !!! this operations holds CS
SSPResult sspdev_send_adress(SSP_IODevice* this, sspdev_addr_t address);
SSPResult sspdev_send_cmdadress(SSP_IODevice* this, uint8_t cmd, sspdev_addr_t address);
SSPResult sspdev_write_cmdadress(SSP_IODevice* this, uint8_t cmd, sspdev_addr_t address);
// send 4byte address
SSPResult sspdev_write_cmdadress4(SSP_IODevice* this, uint8_t cmd, sspdev_addr_t address);

//----------------------------------------------------------------------------
//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);
DevResult sspdev_writeByte(SSP_IODevice* this, sspdev_addr_t address, uint8_t data);
SSPResult sspdev_readData(SSP_IODevice* this, sspdev_addr_t address, void *data, size_t size);
SSPResult sspdev_writeData(SSP_IODevice* this, sspdev_addr_t address, const void *data, size_t size);

//----------------------------------------------------------------------------
SSPResult sspdev_readCmdByte(SSP_IODevice* this, uint8_t cmd, sspdev_addr_t address, void* data);
SSPResult sspdev_writeCmdByte(SSP_IODevice* this, uint8_t cmd, sspdev_addr_t address, uint8_t data);
SSPResult sspdev_readCmdData(SSP_IODevice* this, uint8_t cmd, sspdev_addr_t address, void *data, size_t size);
SSPResult sspdev_writeCmdData(SSP_IODevice* this, uint8_t cmd, sspdev_addr_t address, const void *data, size_t size);




#endif /* BSP_SSP_HAL_H_ */
