/*
 * ssp_hal.hpp
 *
 *  Created on: 15 нояб. 2018 г.
 *      Author: alexrayne
  *      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. *
  ------------------------------------------------------------------------
 */

#ifndef HAL_SSP_HAL_HPP_
#define HAL_SSP_HAL_HPP_

#include "hal_device.h"
#include <cslr.h>
// provides PTResult
//#include <OsSync.h>

/*
 * API SPIdevice  был накидан по мотивам этого драйвера в uOS-embebbed
 * */
class SSP_Device{
public:
    enum ErrorsID{
          errBUSY           = -5
        , errMODE_NOT_SUPP  = -6
        , errBAD_CS         = -7
        , errABORTED        = -8
    };
    // описание конфигурации/режима SPI  для выполнения запроса сообщения
    enum ModeStyleID{
        // Полярность линии синхроимпульсов. Если флаг не задан, то в режиме ожидания
        // линия переводится в низкий уровень; если задан, то в высокий.
            msCPOL    = 1
          , msCLKHI   = msCPOL, msCLKLO   = 0
        // Выбор фронта синхроимпульса, по которому осуществляется выборка данных.
        // Если CPOL не задан, то при заданном CPHA - по заднему, при незаданном - по
        // переднему. Если CPOL задан, то при заданном CPHA - по переднему, при
        // незаданном - по заднему.
        , msCPHA    = 2
        , msCLKFALL = msCPHA, msCLKRISE = 0
        // Выбор порядка следования бит на линиях данных. Если флаг не задан, то
        // первый передаётся старший бит, иначе младший бит.
        , msENDIAN      = 4
        , msLSB_FIRST   = msENDIAN, msMSB_FIRST   = 0
        // Поведение линии выбора устройства после передачи сообщения. Если флаг не
        // задан, то линия переходит в неактивное состояние автоматически. Иначе
        // остаётся активным до передачи сообщения, в поле mode которого будет
        // отсутствовать этот флаг.
        , msCS_HOLD     = 8

        //маска режима SPI
        , msMASK        = 0xff

        //селектор CS абонента
        , msCS_Msk          = 0xff00, msCS_Pos = 8
        // CS == 0 - использую чтобы снять все активные CS
        , msCS_NONE     = 0
        // CS == ~0 - нереальный CS, не управляет линией CS,
        , msCS_KEEP     = msCS_Msk

        // количество бит в слове
        , msNB_Msk          = 0xff0000, msNB_Pos = 16
        , msNB_KEEP         = 0
        , msNB8             = (8<<msNB_Pos)
        , msNB16            = (16<<msNB_Pos)
    };
    static inline unsigned as_msNB(unsigned bits) {return (bits << msNB_Pos)& msNB_Msk;};
    static inline unsigned as_msCS(unsigned cs) {return (cs <<msCS_Pos)&msCS_Msk; }
    static inline unsigned msNB(unsigned mode) {return (mode&msNB_Msk) >> msNB_Pos;};
    static inline unsigned msCS(unsigned mode) {return (mode&msCS_Msk) >>msCS_Pos; }

    enum SpeedID{
        // \ value == 0 , not affect speed
          speedKEEP = 0
        , speedMAX  = ~0u
    };

    //сообщение передаваемое для запроса обмена драйверу
    struct Message{
        // Если поле msg->src равно 0, то в линию должно быть выдано
        //      msg->word_count нулей.
        const void*     src;
        // Если поле msg->dst равно 0, то принятые данные должны быть отброшены.
        void*           dst;
        // Количество слов в сообщении.
        unsigned    word_count;
        // Требуемая частота передачи (битовая скорость) \sa SpeedID
        unsigned    freq;
        // Установки для передачи сообщения. \sa StyleID
        unsigned    mode;

        // обработчик завершения запроса. TODO: не используется
        typedef void (*msg_event)(SSP_Device* self, Message* msg);
        //msg_event   on_complete;
    };

public:
    // BUS lock access
    virtual PTResult aquire(unsigned cs_id){return (PTResult)DEV_NOT_IMPLEMENTED;};
    virtual DevResult release(){return DEV_NOT_IMPLEMENTED;};

    enum { toInfinite = ~0u};

    // trx - неблокирующий запуск транзакции. завершение ее
    // \return errBUSY - если занято другой транзакцией
    //         DEV_BUSY - транзакция в процессе завершения.
    //                      можно дождаться через wait
    virtual DevResult trx(Message& msg){return DEV_NOT_IMPLEMENTED;};
    // ожидает завершения транзакции msg.
    virtual PTResult wait(Message& msg, unsigned to = toInfinite){return (PTResult)DEV_NOT_IMPLEMENTED;};
    // обывает транзакцию
    virtual DevResult abort(Message& msg) {return DEV_NOT_IMPLEMENTED;};
};



//----------------------------------------------------------------------------
// potentional base class for device attaches to ssp port
class SSP_Client {
    public:
        typedef SSP_Device      ssp_t;

        SSP_Client();

        enum {
            mode_default_8bit = ssp_t::msCLKLO | ssp_t::msCLKRISE
                                | ssp_t::msNB8 | ssp_t::msCS_KEEP
        };
        void connect(ssp_t*    _port
                , unsigned mode = mode_default_8bit
                , unsigned freq = ssp_t::speedKEEP)
        {
            port = _port;
            ssp_msg.mode = mode;
            ssp_msg.freq = freq;
        };

        ssp_t* io() const {return port;}
        void mode(unsigned x) {ssp_msg.mode = x;};
        unsigned mode() const {return ssp_msg.mode;};
        void cs(unsigned id) { CSL_FINS(ssp_msg.mode, ssp_t::msCS, id);};
        unsigned cs() const {return CSL_FEXT(ssp_msg.mode, ssp_t::msCS);};
        void freq(unsigned x) {ssp_msg.freq = x;};
        unsigned freq() const     {return ssp_msg.freq;};

        // BUS lock access
        // TODO: может надо бы сделать мутех локальный? чтобы допустить конкурентный
        //      доступ к СПИ других чипов?
        PTResult aquire(unsigned cs_id = 0){
                return io()->aquire(cs_id);
        };
        DevResult release(){
            return io()->release();
        };

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

        // все транзакции read/write могут запускать длительный процесс, который вернет
        //  результат c PT_SCHEDULE(x) == true
        // протонитки могут полить статус завершения транзакции этим вызовом
        PTResult io_ready() {return io()->wait(ssp_msg, 0);};
        PTResult wait(unsigned to = HAL_ASYNCIO_Device::toInfinite){
            return io()->wait(ssp_msg, to);
        };

    public:
        // результаты функций 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 IOResult;
        // helper - констрейнит результаты функций ioXXXX с заданной длиной
        static DevResult io_ASDEVRESULT(int x, int oklen) {
            return (x >= 0)? ((x==oklen)? DEV_OK : DEV_NOK) : x ;
        };

    protected:
        ssp_t*      port;
        ssp_t::Message  ssp_msg;
        // буффер сообщения для драйвера SSP - тут собираю адресные заголовки для
        //  транзакций
        enum { spp_buf_limit = 8};
        union{
            u8          ssp_buf[spp_buf_limit];
            u16         ssp_buf16[spp_buf_limit/2];
            u32         ssp_words[spp_buf_limit/4];
        };

        // функции настройки ssp_msg
        void msg_assign(void *data, const void *src, size_t words){
            ssp_msg.dst = data;
            ssp_msg.src = src;
            ssp_msg.word_count = words;
        };

        void msg_assign_buf(size_t words){
            ssp_msg.dst = ssp_buf;
            ssp_msg.src = ssp_buf;
            ssp_msg.word_count = words;
        };

        // функции отправки ssp_msg -> io()
        // \return  - bytes send amount
        IOResult   msg_trx();
        // неблокирующая отправка ssp_msg
        IOResult   post_trx();
        //
};

//----------------------------------------------------------------------------
// 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)
// - и неблокирующие методы postData/Byte
//
// - формат фрейма можно варьировать - заданием размера адреса, методром style( x )
//   адресная часть может отсылаться прямым (LSB) и обратным (MSB) порядком байт.
//   (обратный порядок более распространен)
//
// - в качетсве простого произвольного универсального обмена даются ioData/Byte() -
//  не-виртуальные методы формирующие запрос к SSP_Device
//
// - методы write/send_cmdadress - отправляют заголовочную часть фрейма - команда+адрес
//
class SSPIO_Device : public SSP_Client
    , public HAL_IO_Device
    , public HAL_IO_Block_Device
    , public HAL_ASYNCIO_Device
{

public:
    typedef HAL_IO_Device   iodevice_i;

    SSPIO_Device();

public:

    // HAL_IO_Device
    using iodevice_i::readByte;
    using iodevice_i::writeByte;
    using iodevice_i::readData;
    using iodevice_i::writeData;
    virtual DevResult readByte(u8 *data);
    virtual DevResult writeByte(u8 data);
    virtual IOResult readData(u8 *data, size_t size);
    virtual IOResult writeData(const u8 *data, size_t size);

public:
    // duplex write-read transfer via SPI

    IOResult ioByte(u8 *dst, u8 data);
    IOResult ioData(void *dst, const void *data, size_t size);

    // makes one transfer with specified mode
    IOResult ioByte(u8 *dst, u8 data, unsigned mode);
    IOResult ioData(void *dst, const void *data, size_t size, unsigned mode);

public:
    using HAL_ASYNCIO_Device::postData;
    // HAL_ASYNCIO_Device
    // \return - bytes send amount
    virtual int postByte(u8 data);
    virtual int postData(const void *data, size_t size);
    virtual int postData(void *dst, const void *data, size_t size);
    virtual PTResult wait_flush(unsigned to = toInfinite);

public:
    // HAL_IO_Block_Device
    enum AdrStyleID{
          addr1BYTE =1
        , addr2BYTE, addr3BYTE, addr4BYTE
        , addrLE    = 0     // MSB first
        , addrBE    = 0x100 // LSB first
        , addrMSB   = addrBE
        , addrLSB   = addrLE
    };
    void style(unsigned /*AdrStyleID*/ astyle) {
        adr_style = astyle;
        adr_mask  = (1<<(8*astyle))-1;
    };
    unsigned addr_bytes() const {return u8(adr_style);};

    //return <0 - some error, >=0 - transfered data amount
    //adress part sends as NetOrder - MSB firts
    virtual DevResult readByte(addr_t address, u8 *data);
    virtual DevResult writeByte(addr_t address, u8 data);
    virtual IOResult readData(addr_t address, u8 *data, size_t size);
    virtual IOResult writeData(addr_t address, const u8 *data, size_t size);

public:
    // !!! this operations holds CS
    IOResult send_adress(addr_t address);
    IOResult send_cmdadress(uint8_t cmd, addr_t address);
    IOResult write_cmdadress(uint8_t cmd, addr_t address);
    // send 4byte address
    IOResult write_cmdadress4(uint8_t cmd, addr_t address);

public:
    IOResult readData(uint8_t cmd, addr_t address, void *data, size_t size);
    IOResult writeData(uint8_t cmd, addr_t address, const void *data, size_t size);

protected:
    unsigned /*AdrStyleID*/  adr_style;
    unsigned    adr_mask;

    // функции настройки ssp_msg
    inline
    void msg_bytes(void *data, const void *src, size_t words){
        msg_assign(data, src, words);
    };

    // return adress bytes adjusted by adress style
    u32  msg_addr(u32 x);
};


#endif /* HAL_SSP_HAL_HPP_ */
