/*
 * flash_hal.hpp
 *
 *  Created on: 26 нояб. 2018 г.
 *      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. *
  ------------------------------------------------------------------------
      FLASH_Device:
            // класс базового управления флешой. прожигание страниц, стирание делается
            //  отдельными командами.

      FLASH_HALDevice: добавлен функционал выбора чипа/устройства/тома флешки

      SPIFlash_GenDevice:
            //  девайс общего стиля - реализует базовый набор команд флешек с
            //          3,4 байтной адресацией

      Flash_Bank:
            //    банк флешек объединен в общее адресное пространство, и реагирует как
            //      одна большая флешка.
 */

#ifndef HAL_FLASH_HAL_HPP_
#define HAL_FLASH_HAL_HPP_

#include "hal_device.h"
#include "ssp_hal.hpp"
#include <OsSync.h>
#include <OsTime.h>
#include <project-conf.h>


//<! добавить измеритель таймаута последней записи
#define FLASHDEV_FEATURE_BUSY_MEASURE   1
//<! добавить проверку разрешения записи чипа.
//   в норме считается что разрешение записи, перед каждой операцие записи, надежно,
//      и не подтверждается
#define FLASHDEV_FEATURE_WE_SURE        2
#ifndef FLASHDEV_STYLE
#define FLASHDEV_STYLE    0
#endif

#if (FLASHDEV_STYLE & FLASHDEV_FEATURE_BUSY_MEASURE)
#define FLASHDEV_BUSY_MEASURE   1
#endif



// класс базового управления флешой. прожигание страниц, стирание делается
//  отдельными командами.
class FLASH_Device
//    : public HAL_IO_Block_Device
{
public:
    // коды результатов предоставляемые драйвером
    enum {
          DEV_WRITE_DISABLED_ERR = -8
        , DEV_NO_SUPPORT_ID_ERR  = -9
        , DEV_OUT_OF_SPACE_ERR   = -10
    };

    // bound - начальная подготовка флеши к работе, сверка ID, и параметров
    virtual DevResult bind() __noexcept {return DEV_NOT_IMPLEMENTED;};

    // методы отсылают команды флеше, как правило не дожидаясь завершения их исполнения
    //  после длительной команды, надо дожидаться готовности флеши через wait_ready
    virtual DevResult write_enable(bool onoff) __noexcept = 0;
    virtual DevResult erase_all() __noexcept = 0;
    virtual DevResult read(unsigned addr, void* dst, unsigned len) __noexcept = 0;
    virtual DevResult verify(unsigned addr, const void* src, unsigned len) __noexcept;

public:
    struct pt oppt;
    // эти методы реализуют сложный автомат с ожиданиями. они вызываются в
    //      PT_SCHEDULE( operationXXX_sectors(....) )
    virtual PTResult erase_sectors(unsigned addr, unsigned len) __noexcept = 0;
    virtual PTResult protect_sectors(unsigned addr, unsigned len, bool onoff) __noexcept {return ptOK;};
    PTResult protect_sectors(bool onoff) {
        return protect_sectors(0, size(), onoff);
    };
    virtual PTResult write(unsigned addr, const void* src, unsigned len) __noexcept = 0;
    virtual PTResult flush() __noexcept {return ptOK;};

public:
    // команды модификации флеши запускают длительные процессы. ожидание их
    //  завершения и готовность флеши надо делать отдельно.
    enum StateID{
          sBUSY     = 1
        , sWE       = 2
        , sERR_ERASE= 0x20
        //, sRDY = 0
#if 1
        // этот флаг всегда передаю со статусом. он позволит отличить статус от
        //      PTResult_t
        , sSTATUS    = 0x10000
#else
        , sSTATUS    = 0
#endif
    };

    // \return < 0 - error DevResult code
    //         >= 0 - flash status register
    virtual int  state() = 0;

    static bool is_busy(int x){
        if (x < 0)
            return false;
        return ((x & sBUSY) != 0);
    }

    static bool is_ready(int x){
        if (x < 0)
            return false;
        return ((x & sBUSY) == 0);
    }

    static bool is_error(int x){
        if (x < 0)
            return true;
        return ((x & sERR_ERASE) != 0);
    }

    static bool is_writable(int x){
        if (x < 0)
            return false;
        return ((x & sWE) != 0);
    }

public:
    typedef unsigned long timeout_value_t;
    enum {
        toInfinite  = TO_INFINITE
            // время поллинга статуса BUSY [в тиках ОС]
        , toBUSYPoll= 2
            // короткие тайминги ожтдаются быстрым полингом - спином
            // количестко спинов можно вводить в таймаут  wait_ready
            //      например: wait_ready( OSTicksMS(50) + 5*toSPIN )
            //          перед длинным поллингом периодом toBUSYPoll
            //          будет сделан быстрый спин-полинг в 5 опросов
        , toSPIN    = 0x10000
        , toMASK    = (toSPIN-1)
        , toSPIN_Pos= 16
        , toSPIN_Msk= 0xff0000

        // если таймаут не использует ожидания в мс, используется только спин-ожидание
        //  то циклическая операция \sa cycles(), старается делать паузы,
        // отдавая время другим задачам. дистанцию пауз - задаю общим количеством
        //      спинов
        , toYIELD_LEN_SPINS = 100
        // в таймаут можно заложить явно, как вставлять паузы
        , toYIELD           = 0x1000000ul
        , toYIELD_Pos       = 24
        , toYIELD_Msk       = (0xfful<<toYIELD_Pos)
    };

    // \return - state возвращает статус флеши.
    //           статус флеши всегда содержит флаг sSTATUS,
    //           результат без этого флага означает что ожидание в процессе
    // \return < 0 - код ошибки DevResult
    virtual int  wait_ready(timeout_value_t waittime = toInfinite) __noexcept;
    virtual int  wait_ready(os_timeout_t* to) __noexcept;
    //virtual DevResult min_address() const {return 0;};

    // wait_ready(unsigned ) использует этот таймаут, избегаю создавать его
    //      на стеке, чтобы уметь работать в протонитке
    os_timeout_t to_wait;

    //------------------------------------------------------------------------
    // помошники для работы с полями timeout_value_t
    static inline
    unsigned to_ticks(timeout_value_t x) {return x & toMASK;};
    static inline
    unsigned to_spins(timeout_value_t x) {return ((x& toSPIN_Msk) >> toSPIN_Pos);};
    static inline
    unsigned to_yields(timeout_value_t x) {return ((x & toYIELD_Msk) >> toYIELD_Pos);};

public:
    typedef     uint64_t    flashsize_t;
    flashsize_t size() const {return _size;};
    u32         sec_size() const {return _sec_size;};
    // \return индекс сектора по адресу addr
    unsigned    sec_of( unsigned addr) const {return addr >> sec_width;};
    // \return true - if addr ... + len places in one page
    bool is_single_page(unsigned addr, unsigned len) const;

    // описание параметров флеши
    struct Describe {
        unsigned    nb_sectors;
        unsigned    nb_pages_in_sector;
        unsigned    page_size;
        timeout_value_t    burn_page_ticks;
        timeout_value_t    erase_ticks;
        timeout_value_t    erase_all_ticks;
        unsigned    sec_size() const {return page_size*nb_pages_in_sector;};
        unsigned    chip_size() const {return nb_sectors*sec_size();};
    };
    const Describe& info() const {return *_info;};

    // флешки имеют ID от производителя, по которому можно сверить/определить
    //  ее параметры. Размеры ID вариабельны.
    typedef const u8* id_str;
    virtual id_str id() {return NULL;};

    //int         direct_read;
    //unsigned    data_align;
protected:
    // назначает описание флешки, и подготавливает необходимые настройки
    void  assign_info(const Describe* x);
    //это описание должно быть обязательно заполняемо, NULL недопустим
    const Describe* _info;
    // cached value of page_size*nb_pages_in_sector*nb_sectors
    flashsize_t     _size;
    u32             _sec_size;
    // sector adress bit width
    u8              sec_width;

public:
    typedef u32 page_t;
    typedef DevResult (FLASH_Device::*page_op)(void* data, page_t page, unsigned size);
    typedef page_op sec_op;
    // cycle operation through multiple pages
    // USAGE:
    //      cycle_sectors( D, P, L, OP);
    //      PT_SCHEDULE_RESULT_WHILE( pt, res, cycles() );

    // цикл над многими страницами некоторой базовой операции.
    // !!! пересечения page границы памяти ведет к заворачиванию адреса
    PTResult cycles();
    // перед запуском цикл надо назначить
    void cycle_pages(void* data, page_t page, unsigned len);
    void cycle_sectors(void* data, page_t page, unsigned len);
    void cycle_op(page_op op, timeout_value_t to);

protected:
    // все сложные операции имеют контекст своей работы, он здесь
    struct cycctx_t {
            struct pt   pt;
            page_t      page;
            unsigned    len;
            page_op     op;
            u8*         cur_data;
            unsigned    cur_size;
            unsigned    page_size;
            unsigned    adr_mask;
            // таймаут гтовности каждого цикла
            timeout_value_t     cycle_to;
            // длительный цикл надо перемежать паузами чтобы отдать процессор
            // другим ниткам. это длительность серии, до перерыва.
            unsigned    cycle_yield_to;
            unsigned    cycle_yield;

            void        init(void* dst, page_t page, unsigned len, unsigned pagesz);
            // функции cycle_XXX - инициируют/назначают циклический вызов протонитки
            //      запущеная операция далее исполняться этим вызовом.
            DevResult   cycle_one(FLASH_Device* self);
    } cycx;

#if FLASHDEV_BUSY_MEASURE
    // для отладки флеши ввожу некоторые измерения:
    // измерение длительности BUSY от флешки на последней операции
public:
    timeout_value_t busy_time_os;
    int             busy_time_polls;

protected:
    void            busy_start() {
        busy_time_polls = 0;
        busy_time_os    = clock_now();
    }
    void            busy_polled(){
        if (busy_time_polls<= 0)
            busy_time_polls--;
    }
    void            busy_done(){
        busy_time_os    = clock_now() - busy_time_os;
        busy_time_polls = -busy_time_polls;
    }
#else
    void            busy_start(){};
    void            busy_polled(){};
    void            busy_done(){};
#endif

};



class FLASH_HALDevice
        : public FLASH_Device
        , public HAL_Device
{
public:
    FLASH_HALDevice(dev_name name):HAL_Device(name){};
    // \arg cs - линия выбора флешки
    enum {csNONE = 0};
    virtual DevResult select(unsigned csid) = 0;
    DevResult   release() {return select(csNONE); };
};

//----------------------------------------------------------------------------
//  девайс общего стиля - реализует базовый набор команд флешек с
//          3,4 байтной адресацией
class SPIFlash_GenDevice
    : public FLASH_HALDevice
    , public SSPIO_Device
{
public:
    typedef  FLASH_HALDevice inherited;
    typedef  SSPIO_Device io_t;

    SPIFlash_GenDevice(dev_name name);

    virtual DevResult init();
    virtual DevResult deinit();
    // \arg cs - линия выбора флешки
    virtual DevResult select(unsigned csid = 0) {
        cs(csid);
        return DEV_OK;
    };

public:
    //FLASH_Device
    virtual DevResult erase_all();
    virtual int       state();
    virtual DevResult read(unsigned addr, void* dst, unsigned len);
    virtual DevResult write_enable(bool onoff);

public:
    virtual PTResult erase_sectors(unsigned sec, unsigned len);
    virtual PTResult write(unsigned addr, const void* src, unsigned len);
    virtual PTResult flush();

public:
    // SPI флешки наследуют общий набор команд
    enum FlashCmdId{
          CMD_WRDI      =  0x04     // Write Disable
        , CMD_WREN      =  0x06     // Write Enable
        , CMD_WRSR      =  0x01     // Write Register (Status-1, Configuration-1)
        , CMD_RDSR      =  0x05     // Read Status Register-1
        , CMD_READ      =  0x03     // Read (3- or 4-byte address)
        , CMD_FAST_READ =  0x0B     // Fast Read (3- or 4-byte address)
        , CMD_4FAST_READ=  0x0C     // Fast Read 4-byte address
        , CMD_PP        =  0x02     // Page Program (3- or 4-byte address)
        , CMD_SE_64K    =  0xD8     // Erase 64 kB or 256 kB (3- or 4-byte address)
        , CMD_SE_32K    =  0x52     // Erase 32 kB
        , CMD_SE_4K     =  0x20     // Erase sector
        , CMD_BE        =  0x60     // Bulk Erase
        , CMD_BE2       =  0xC7     // Bulk Erase (alternate command)
        , CMD_RDID      =  0x9F     // Read ID (JEDEC Manufacturer ID and JEDEC CFI)
        , CMD_RES       =  0xAB     // Read Electronic Signature
        , CMD_RESET     =  0xF0     // Software Reset
    };
    enum StatusFlagID {
          SR_WIP       =  (1 << 0) // Write in Progress
        , SR_WEL       =  (1 << 1) // Write Enable Latch
        , SR_E_ERR     =  (1 << 5) // Erase Error Occurred
    };

public:
    // базовые операции со страницами. Они не обязаны корректно пересекать
    //  границы страниц
    DevResult write_page(void* src, page_t page, unsigned len);
    DevResult read_page(void* dst, page_t page, unsigned len);
    DevResult erase_sec(void* dst, page_t page, unsigned len);

protected:
    int     status_cache;
    // this is
    int     CMD_SE;  //CMD_SE selected by chip conf

    // все сложные операции имеют контекст своей работы, он здесь
    union ctx_t {
        struct {
            //unsigned secsz;
            unsigned from;
            unsigned len;
        }erase;
    } ctx;
};



//----------------------------------------------------------------------------
//  По умолчанию SR_WEL сбрасывается после большинства операций модификации.
//  Чтобы не дергать постоянно write_enable, этот класс кеширует
//  последнее состояние WEL, и сам подает флешке write_enable по всякую операцию
//  записи.
//  ИСПОЛЬЗОВАНИЕ:
//  перед началом изменения флеши надо один раз вызвать write_enable(true);
//  запрет записи делается явным вызовом write_enable(false)

class SPIFlash_WritableDevice
    : public SPIFlash_GenDevice
{
public:
    typedef SPIFlash_GenDevice inherited;
    SPIFlash_WritableDevice(dev_name name) : inherited(name){};

    // все операции модификации снимают разрешение записи (WE) после выполнения.
    //  Поэтому статус WE сделаю полем класса, и все операции модификации
    //  предваряю разрешением записи в соответсвии с полем.
    virtual DevResult write_enable(bool onoff);

    // текущий статус WEL, не обновляет статус флеши.
    bool    is_WEL() const {return (status_cache & SR_WEL) != 0;};

    // state должен учитывать статус WE
    virtual int       state();
    virtual DevResult erase_all();
    virtual PTResult erase_sectors(unsigned page, unsigned len);
    virtual PTResult write(unsigned page, const void* src, unsigned len);

public:
    DevResult erase_sec(void* dst, page_t page, unsigned len);
    DevResult write_page(void* src, addr_t page, unsigned len);
protected:
    DevResult enable_wr();
    DevResult check_wr( int ioresult );
};



//----------------------------------------------------------------------------
//    банк флешек объединен в общее адресное пространство, и реагирует как
//      одна большая флешка.
class Flash_Bank
    : public FLASH_HALDevice
{
public:
    typedef FLASH_HALDevice    inherited;
    typedef FLASH_HALDevice    flash_t;
    typedef const u8*       bank_list;

    Flash_Bank(dev_name name);

    virtual int init();
    virtual int deinit();
    // Банк не требует внешнего управления своими селекторами, его методы управляются
    //  с селекторами самостоятельно
    virtual DevResult select(unsigned csid = 0) {return DEV_OK;};

    // соединяет банк с драйвером флешки, и селектором флешек
    // \arg banks - Zstring chip banks - перечень селекторов флешек на шине SPI
    DevResult connect(flash_t* _io, bank_list _banks = NULL);

    flash_t*  io() const {return _io;};
    // Zstring chip banks - перечень селекторов флешек на шине SPI
    bank_list banks() const {return _banks;};
    void      banks(bank_list _banks);
    const Describe& info() const {return bank_info;}

public:
    using inherited::protect_sectors;
    //FLASH_Device
    virtual DevResult bind() __noexcept;
    virtual DevResult write_enable(bool onoff) __noexcept;
    virtual DevResult erase_all() __noexcept;
    virtual int  state() __noexcept;

    // нужно для ожидания статуса отдельного банка
    int  state_bank(unsigned page);
    // \return - state возвращает статус флеши.
    //           статус флеши всегда содержит флаг sSTATUS,
    //           результат без этого флага означает что ожидание в процессе
    // \return < 0 - код ошибки DevResult
    int  wait_bank(unsigned page, timeout_value_t to = toInfinite);

public:
    struct pt oppt;
    virtual PTResult protect_sectors(unsigned page, unsigned len, bool onoff) __noexcept;
    virtual PTResult erase_sectors(unsigned from, unsigned num) __noexcept;
    virtual PTResult write(unsigned page, const void* src, unsigned len) __noexcept;
    virtual DevResult read(unsigned page, void* dst, unsigned len) __noexcept;
    virtual DevResult verify(unsigned page, const void* src, unsigned len) __noexcept;
    virtual PTResult flush() __noexcept;

protected:
    flash_t*    _io;
    bank_list   _banks;
    unsigned    banks_num;
    Describe    bank_info;
    // размер адреса чипа [биты]
    u8          bank_width;

    typedef     unsigned    addr_t;
    typedef     unsigned    page_t;
    typedef     unsigned    bank_t;
    bank_t      sec_bank(addr_t sec) const;
    bank_t      addr_bank(addr_t page) const;
    page_t      addr_page(addr_t page) const{
        const addr_t page_mask = io()->size()-1;
        return page & page_mask;
    }
    bool is_single_bank(addr_t page, unsigned len) const{
        return ( addr_bank(page) == addr_bank(page+len-1) );
    }
    DevResult select_bank_page(addr_t page);

    typedef PTResult (Flash_Bank::*bank_op)(void* data, addr_t page, unsigned size);
    void cycle_banks(void* data, addr_t page, unsigned size, bank_op op);
    PTResult cycles();

    PTResult write_op(void* data, addr_t page, unsigned size);
    PTResult read_op(void* data, addr_t page, unsigned size);
    PTResult verify_op(void* data, addr_t page, unsigned size);
    PTResult protect_op(void* data, addr_t page, unsigned size);
    PTResult unprotect_op(void* data, addr_t page, unsigned size);
    PTResult erase_op(void* data, addr_t page, unsigned size);
};


#endif /* HAL_FLASH_HAL_HPP_ */
