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

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

 */

#ifndef BSP_HAL_FLASH_HAL_H_
#define BSP_HAL_FLASH_HAL_H_

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



typedef uint32_t flash_addr_t;
//typedef uint64_t    flashsize_t;
typedef flash_addr_t  flashsize_t;

typedef flash_addr_t flash_page_t;
typedef flash_page_t flash_sector_t;

struct FLASH_Device;
typedef struct FLASH_Device FLASH_Device;


//================================================================================
// коды результатов предоставляемые драйвером FLASH_Device
enum {
      DEV_WRITE_DISABLED_ERR = -20
    , DEV_NO_SUPPORT_ID_ERR  = -21
    , DEV_OUT_OF_SPACE_ERR   = -22
    , DEV_NO_SUPPORT_IDMEM_ERR  = -23
    , DEV_NO_MEM_ERR        = -24       // нет устройства на шине
};


#ifndef HAL_FLASH_BUSY_POLL_TO
#define HAL_FLASH_BUSY_POLL_TO  2
#endif

//================================================================================
// команды модификации флеши запускают длительные процессы. ожидание их
//  завершения и готовность флеши надо делать отдельно.
enum FlashStateID{
      FLASH_sBUSY       = 1
    , FLASH_sWE         = 2
    , FLASH_sERR_ERASE  = 0x20
    //, FLASH_sRDY = 0

#if 1
    // этот флаг всегда передаю со статусом. он позволит отличить статус от
    //      PTResult_t
    , FLASH_sSTATUS    = 0x10000
#else
    , FLASH_sSTATUS    = 0
#endif

    // application Write Enable Lock, SPI Flash provides byte of status, and
    //  aplication can override FLASH_sWE with this flag, demanding enforce enabling
    //  write for writes
    , FLASH_sWELOCK    =  0x20000
};

typedef int FlashState;

static inline
bool flash_is_status(FlashState x){
    if (x < 0)
        return false;
    return ((x & FLASH_sSTATUS) != 0);
}

static inline
bool flash_is_busy(FlashState x){
    if (x < 0)
        return false;
    return ((x & FLASH_sBUSY) != 0);
}

static inline
bool flash_is_ready(FlashState x){
    if (x < 0)
        return false;
    return ((x & FLASH_sBUSY) == 0);
}

static inline
bool flash_is_error(FlashState x){
    if (x < 0)
        return true;
    return ((x & FLASH_sERR_ERASE) != 0);
}

static inline
bool flash_is_writable(FlashState x){
    if (x < 0)
        return false;
    return ((x & (FLASH_sWE|FLASH_sWELOCK)) != 0);
}

/// @brief check x - is flash ready status
bool flash_sure_ready(FlashState x);
/// @brief check x - is flash ready writable status
bool flash_sure_ready_write(FlashState x);
/// @brief check x - is flash ready status
bool flash_sure_busy(FlashState x);



//================================================================================
typedef unsigned long flash_timeout_t;

/** @brief тики таймаута флеш записываю в формате 16бит float: m14e2
 *      старшие 2бит обозначают ^:2 экспоненту, младшие мантиссу
 *      число N >= 0x4000-> обозначают (N<<1)[tick]
 *      число N >= 0x8000-> обозначают (N<<2)[tick]
 *      число N >= 0xc000-> обозначают (N<<3)[tick]
 * используейте t = FLASHTO_AS_TICK(N) или N = TICK_AS_FLASHTO(t) для перевода единиц
 * */
typedef unsigned      flashto_t;

/// @brief перевод [tick] ->[flashto]
flashto_t tick_as_flashto( unsigned t);
/// @brief перевод [flashto] -> [tick]
unsigned flashto_as_ticks( flashto_t to);

#define TICK_AS_FLASHTO( t )   ( ((t) < 0x4000u)? (t) : \
                                 ((t) < (0x4000u<<1) )? ( 0x4000u | ((t)+1)>>1) : \
                                 ((t) < (0x4000u<<2) )? ( 0x8000u | ((t)+3)>>2) : \
                                 ((t) < (0x4000u<<3) )? ( 0xc000u | ((t)+7)>>3) : \
                                 FLASH_toInfinite  )



enum {

    //младшие 16бит обозначаюттаймаут в тиках формата 14n2e - с 2бит расширением:

    FLASH_toInfinite    = (~0ul), //TO_INFINITE ,

    // время поллинга статуса BUSY [в тиках ОС]
    FLASH_toBUSYPoll    = HAL_FLASH_BUSY_POLL_TO ,


        // короткие тайминги ожтдаются быстрым полингом - спином
        // количестко спинов можно вводить в таймаут  wait_ready
        //      например: wait_ready( OSTicksMS(50) + 5*toSPIN )
        //          перед длинным поллингом периодом toBUSYPoll
        //          будет сделан быстрый спин-полинг в 5 опросов
    FLASH_toSPIN        = 0x10000 ,
    FLASH_toMASK        = (FLASH_toSPIN-1),
    FLASH_toSPIN_Pos    = 16 ,
    FLASH_toSPIN_Msk    = 0xff0000 ,

    // если таймаут не использует ожидания в мс, используется только спин-ожидание
    //  то циклическая операция \sa cycles(), старается делать паузы,
    // отдавая время другим задачам.

    // TODO: можно использовать toYIELD для задания периода поллинга переопределяя FLASH_toBUSYPoll

    // в таймаут можно заложить явно, через сколько циклов вставлять паузы
    FLASH_toYIELD           = 0x1000000ul ,
    FLASH_toYIELD_Pos       = 24 ,
    FLASH_toYIELD_Msk       = (0xfful<<FLASH_toYIELD_Pos) ,

    // если в атймауте не задано и дистанция пауз,то задаю её общим количеством спинов
    //  через которые пускаю паузу
    FLASH_toYIELD_LEN_SPINS = 100,
};



//------------------------------------------------------------------------
// помошники для работы с полями timeout_value_t
static inline
unsigned flash_to_ticks(flash_timeout_t x) {return x & FLASH_toMASK;};
static inline
unsigned flash_to_spins(flash_timeout_t x) {return ((x& FLASH_toSPIN_Msk) >> FLASH_toSPIN_Pos);};
static inline
unsigned flash_to_yields(flash_timeout_t x) {return ((x & FLASH_toYIELD_Msk) >> FLASH_toYIELD_Pos);};
static inline
unsigned flash_to_noyields(flash_timeout_t x) {return (x & ~FLASH_toYIELD_Msk);};



//================================================================================
// описание параметров флеши
struct FlashChipDescribe {
    unsigned    nb_sectors;
    unsigned    nb_pages_in_sector;
    unsigned    page_size;
    flash_timeout_t    burn_page_ticks;
    flash_timeout_t    erase_ticks;
    flash_timeout_t    erase_all_ticks;
};
typedef struct FlashChipDescribe FlasChiphDescribe;

static inline
unsigned    flashchip_sec_size(const FlasChiphDescribe* x) {return x->page_size * x->nb_pages_in_sector;};

static inline
flashsize_t flashchip_chip_size(const FlasChiphDescribe* x) {return x->nb_sectors* flashchip_sec_size(x);};



//================================================================================

typedef DevResult (*flash_page_op)(FLASH_Device*, void* data, flash_page_t page, unsigned size);
typedef flash_page_op flash_sec_op;

// все сложные операции имеют контекст своей работы, он здесь
struct FlashCycling {
        struct pt       pt;
        flash_page_t    page;
        unsigned        len;
        flash_page_op   op;
        uint8_t*        cur_data;
        unsigned        cur_len;    //< transferred data progress
        //< current page transfer size.
        //  @value == 0 - only wait operation, nothing to do, just leave cycle on 1st invoke
        //              single-wait operations should use cur_size=0
        unsigned        cur_size;
        unsigned        page_size;
        unsigned        adr_mask;
        // таймаут готовности каждого цикла.
        //   для операций чтения этот таймаут == 0, они завершаются по завершении обмену с флешой сразу.
        flash_timeout_t     cycle_to;
        // длительный цикл надо перемежать паузами чтобы отдать процессор
        // другим ниткам. это длительность серии, до перерыва.
        unsigned        cycle_yield_to;
        unsigned        cycle_yield;
};
typedef struct FlashCycling FlashCycling;

void        flash_cycling_init(FlashCycling* this, void* dst, flash_page_t page, unsigned len, unsigned pagesz);

/// @brief  init cycling wait last op finish
void        flash_cycling_wait_init(FlashCycling* this, flash_page_t page);

// функции cycle_XXX - инициируют/назначают циклический вызов протонитки
//      запущеная операция далее исполняться этим вызовом.
DevResult   flash_cycling_one(FlashCycling* this, FLASH_Device* self);



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

struct FLASH_Device{

    // bound - начальная подготовка флеши к работе, сверка ID, и параметров
    DevResult (*bind)(FLASH_Device* this);

    // выбирает девайс на общей шине
    DevResult (*select)(FLASH_Device* this, unsigned csid);

    // выбирает девайс на шине для адреса. Банки чипов предоставляют его. flash-API начинает им работу в методах:
    //      flash[_wait]_read/write, flash_xxx_pages
    DevResult (*select_addr)(FLASH_Device* this, flash_addr_t addr);


    // методы отсылают команды выбраной флеше, как правило не дожидаясь завершения их исполнения
    //  после длительной команды, надо дожидаться готовности флеши через wait_ready
    DevResult (*write_enable)(FLASH_Device* this, bool onoff);
    DevResult (*erase_all)(FLASH_Device* this);


    /* эти методы реализуют сложный автомат с ожиданиями.
     * если они не возвращают сразу результат, то значит запускают цикл по страницам,
     *      который можно ожидать полингом flash_cycles(self)
     *      можно дожидаться так PT_SCHEDULE( flash_cycles(self) )
     * @sample
            ok = flashstore.flash.read(&flash, adr, data, len );
            if ( PT_SCHEDULE(ok) )
                PROCESS_SCHEDULE_RESULT( ok, flash_cycles(&flash) );
     */
    PTResult (*read)(FLASH_Device* this, flash_addr_t addr, void* dst, unsigned len);
    PTResult (*verify)(FLASH_Device* this, flash_addr_t addr, const void* src, unsigned len);



    /* эти операции сразу работают на нитке <oppt>.
    //  их надо запускать по типу:
    //      PROCESS_PT_SPAWN( &eeprom.flash.oppt,  eeprom.flash.write(&eeprom.flash, addr, &tmp, 1) );
    */
    PTResult (*erase_sectors)(FLASH_Device* this, flash_addr_t addr, unsigned len);
    PTResult (*protect_sectors)(FLASH_Device* this, flash_addr_t addr, unsigned len, bool onoff);
    PTResult (*write)(FLASH_Device* this, flash_addr_t addr, const void* src, unsigned len);
    PTResult (*flush)(FLASH_Device* this);
    struct pt oppt;



    // \return < 0 - error DevResult code
    //         >= 0 - flash status register
    FlashState  (*state)(FLASH_Device* this);

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

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


    // флешки имеют ID от производителя, по которому можно сверить/определить
    //  ее параметры. Размеры ID вариабельны.
    const uint8_t* id;

    //это описание должно быть обязательно заполняемо, NULL недопустим
    const FlasChiphDescribe* info;
    // cached value of page_size*nb_pages_in_sector*nb_sectors
    flashsize_t     _size;
    uint32_t        _sec_size;
    // sector adress bit width
    unsigned        sec_width;

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

    //protected:
    FlashState      status_cache;
    FlashCycling    cycx;

};

void  flash_init(FLASH_Device* this);

// назначает описание флешки, и подготавливает необходимые настройки
void  flash_assign_info(FLASH_Device* this, const FlasChiphDescribe* x);



static inline
flashsize_t flash_size(const FLASH_Device* this) {return this->_size;};

static inline
uint32_t    flash_sec_size(const FLASH_Device* this) {return this->_sec_size;};

// \return индекс сектора по адресу addr
static inline
unsigned    flash_sec_of(const FLASH_Device* this, flash_addr_t addr) {return addr >> this->sec_width;};

// \return true - if addr ... + len places in one page
bool flash_in_single_page(const FLASH_Device* this, flash_addr_t addr, unsigned len);
bool flash_in_single_sec(const FLASH_Device* this, flash_addr_t addr, unsigned len);

/** @return len from addr -> page bound
 * */
static inline
unsigned flash_align_len(flash_addr_t addr, unsigned pagelen){
    unsigned sec_mask = pagelen-1;
    return pagelen - (addr & sec_mask);
}



FlashState  flash_wait_ready(FLASH_Device* this, flash_timeout_t waittime);
FlashState  flash_wait_ready_to(FLASH_Device* this, os_timeout_t* to);


/// @brief проверяет занятость флеши:
///         - активен запрос ssp @sa this->flush
///         - активна нитка this->oppt
///         - активна нитка flash_cycles
/// @return ptWAITING - занята флеша
///
/// @note   после завершения текущей операции, необходимо не забыть уведомить остальне нитки,
///         чтобы они могли перепроверить flash_is_wai
PTResult flash_is_wait(FLASH_Device* this);




// текущий статус WEL, не обновляет статус флеши.
static inline
bool    flash_is_WEL(FLASH_Device* this) {
    return (this->status_cache & (FLASH_sWELOCK | FLASH_sWE) ) != 0;
};

// установка занятости флеша делается после отправки соответствующих операций,
//      позволяет видет актуальный статус флеша еще до опроса его статуса.
static inline
void  flash_force_busy(FLASH_Device* this) {
    this->status_cache |= FLASH_sBUSY;
};



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

/** неблокирующее чтение на нитке <oppt> - включает ожидание готовности флеши
 * @usage
 *  PROCESS_PT_SPAWN( &eeprom.flash.oppt,  flash_wait_read(&eeprom.flash, addr, &tmp, 1) );
 *
 * @param this
 * @param addr
 * @param dst
 * @param len
 * @return ptYIELDED - операция в процессе запуска
 *         ptWAITING - операция запущена в flash_cycles, и завершить её можно PT_SCHEDULE( flash_cycles(self) )
 *                  !!! в этом случае надо завершить oppt
 */
PTResult flash_wait_read(FLASH_Device* this, flash_addr_t addr, void* dst, unsigned len);

// блокирующее чтение - включает ожидание flash_cycles, если требует драйвер
DevResult flash_read(FLASH_Device* this, flash_addr_t addr, void* dst, unsigned len);



/// @brief удобный акроним
PTResult  flash_protect_all_sectors(FLASH_Device* this, bool onoff);




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

/** цикл над многими страницами некоторой базовой операции. перед началом страничной операции
 *      дожидается готовности флеши wait_ready.
 * !!! пересечения page границы памяти ведет к заворачиванию адреса
 * этот цикл могут запускать оперции Flash_Device:
 *      - read, verify
 *      - write, flush,
 *      - erase_sectors, protect_sectors
 * @return - последняя операция цикла не дожидается завершения!!!, требуется дождатья её
 *           через wait_ready!!!
 */
PTResult flash_cycles(FLASH_Device* this);

// после обрыва цикла, только ожидается завершение последней транзакции SSP
void flash_cycles_abort(FLASH_Device* this);




/// @brief Это операции для конструирования цикла flash_cycles
void flash_cycle_pages(FLASH_Device* this, void* data, flash_page_t page, unsigned len);
void flash_cycle_sectors(FLASH_Device* this, void* data, flash_sector_t page, unsigned len);
void flash_cycle_op(FLASH_Device* this, flash_page_op op, flash_timeout_t to);


/// @brief  это типовые функции-обертки оперций флеш,  для использвоания в flash_cycles
//--------------------------------------------------------------------------------------------
PTResult  flash_read_pages(FLASH_Device* self, flash_addr_t page, void* dst, unsigned len
                            , flash_page_op ask_page);
/* в основном флеши предоставляют произвольный доступ чтения - любой кусок можно прочитать за раз, пересекая страницы
 * */
PTResult  flash_read_one(FLASH_Device* self, flash_addr_t page, void* dst, unsigned len
                            , flash_page_op ask_page);

// нитка постраничной записи
PTResult flash_write_pages(FLASH_Device* self, flash_addr_t page, const void* src, unsigned len
                            , flash_page_op page_write);

PTResult flash_erase_sectors(FLASH_Device* self, flash_addr_t _from, unsigned _len
                            , flash_page_op erase_sec);




/////////////////////////////////////////////////////////////////////////////////////
#if FLASHDEV_BUSY_MEASURE
    // для отладки флеши ввожу некоторые измерения:
    // измерение длительности BUSY от флешки на последней операции
static inline
void            flash_busy_start(FLASH_Device* this) {
    this->busy_time_polls = 0;
    this->busy_time_os    = clock_now();
}

static inline
void            flash_busy_wait(FLASH_Device* this) {
    os_timeout_t* to = &this->to_wait;
    this->busy_to    = ostimeout_least(to);
}

/*пока полинг тикает busy_time_polls < 0, по завершении полинга он переворачивается  */
static inline
void            flash_busy_polled(FLASH_Device* this){
    if (this->busy_time_polls<= 0)
        --(this->busy_time_polls);
}

static inline
void            flash_busy_done(FLASH_Device* this){
    this->busy_time_os    = clock_now() - this->busy_time_os;
    this->busy_time_polls = - (this->busy_time_polls);
}
#else
#define flash_busy_start(this)
#define flash_busy_wait(this)
#define flash_busy_polled(this)
#define flash_busy_done(this)
#endif







#endif /* BSP_HAL_FLASH_HAL_H_ */
