/*
 * flash_hal.c
 *
 *  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. *
 */

#include <string.h>
#include "flash_hal.h"
#include "ptx.h"
#include "OsProcess.h"


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


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

#ifndef trace_flash_pause
trace_need(flash_pause)
#endif

#ifndef trace_flash_cycles
trace_need(flash_cycles)
#endif




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

/// @brief перевод [tick] ->[flashto]

flashto_t tick_as_flashto( unsigned t){
    return TICK_AS_FLASHTO(t);
}

/// @brief перевод [flashto] -> [tick]
unsigned flashto_as_ticks( flashto_t to){
    return (to & 0x3fffu) << (to >> 14);
}



/// @brief check x - is flash ready status
bool flash_sure_ready(FlashState x){
    if ( !flash_is_status(x) )
        return false;
    if ( flash_is_error(x) )
        return false;

    return flash_is_ready(x);
}

/// @brief check x - is flash ready writable status
bool flash_sure_ready_write(FlashState x){
    if ( !flash_sure_ready(x) )
        return false;

    return  flash_is_writable(x);
}

bool flash_sure_busy(FlashState x){
    if ( !flash_is_status(x) )
        return false;

    return flash_is_busy(x);
}


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

// назначает описание флешки, и подготавливает необходимые настройки
void  flash_assign_info(FLASH_Device* this, const FlasChiphDescribe* x){
    ASSERT(x != NULL);
    this->info       = x;
    this->_sec_size   = flashchip_sec_size(x);
    this->_size       = flashchip_chip_size(x);
    ASSERT(this->_sec_size > 0);
    this->sec_width   = ctz(this->_sec_size);
}


// \return true - if addr ... + len places in one page
bool flash_in_single_page(const FLASH_Device* this, flash_addr_t addr, unsigned len){
    //!!!WARN: этот код работает только со страницами размера 2^n
    ASSERT(this->info != NULL);
    unsigned mask = ~(this->info->page_size-1);
    return ( (addr&mask) == ((addr+len-1)&mask) );
}

bool flash_in_single_sec(const FLASH_Device* this, flash_addr_t addr, unsigned len){
    //!!!WARN: этот код работает только со страницами размера 2^n
    ASSERT(this->info != NULL);
    unsigned mask = ~(this->_sec_size-1);
    return ( (addr&mask) == ((addr+len-1)&mask) );
}



//virtual
FlashState  flash_wait_ready(FLASH_Device* this, flash_timeout_t waittime) {
    os_timeout_t* to = &this->to_wait;
    if ( ostimeout_active(to) )
        return this->wait_ready_to(this, to);

    // timeout starts only after last operation have pended
    PTResult free = this->flush(this);
    if ( PT_SCHEDULE(free) )
        return PT_WAITING;

    FlashState status = this->state(this);
    flash_busy_polled(this);

    if ( (flash_is_ready(status)) || (status < 0) ){
        flash_busy_done(this);
        return status;
    }

    // spin wait for fast operatons
    if (waittime != FLASH_toInfinite) {
        unsigned spin = flash_to_spins(waittime);
        for (; flash_is_busy(status) && (spin > 0) ; spin--){
            status = this->state(this);
            flash_busy_polled(this);
        }
    }

    if (flash_is_ready(status)){
        flash_busy_done(this);
        return status;
    }

    if ( (waittime & FLASH_toMASK) == 0)
        return status;

    ostimeout_init(to, flashto_as_ticks( flash_to_ticks(waittime) ) );
    flash_busy_wait(this);
    return this->wait_ready_to(this, to);
}

// ожидание готовности флешки веду поллингом с периодом toBUSYPoll
//virtual
FlashState  flash_wait_ready_to(FLASH_Device* this, os_timeout_t* to) {
    os_timeout_t* to_poll = &this->to_poll;

    if ( ostimeout_waiting( to_poll) )
        return PT_WAITING;

    // timeout starts only after last operation have pended
    PTResult free = this->flush(this);
    if ( PT_SCHEDULE(free) )
        return PT_WAITING;

    int status = this->state(this);
    flash_busy_polled(this);

    for (; flash_is_busy(status); status = this->state(this) )
    {
        if (ostimeout_expired(to))
            break;

        if ( ostimeout_waiting( to_poll) )
            return PT_WAITING;

        ostimeout_init(to_poll, FLASH_toBUSYPoll);
        return PT_WAITING;
    }
    flash_busy_done(this);

    ostimeout_stop(to);
    ostimeout_stop(to_poll);

    return status;
}



/////////////////////////////////////////////////////////////////////////////////////
void        flash_cycling_init(FlashCycling* this, void* data, flash_page_t _page, unsigned _len, unsigned secsz)
{
    unsigned sec_mask = secsz-1;

    this->cur_size = secsz;
    this->len = _len;
    this->page_size = secsz;
    this->page      = _page;

    if (secsz > 0 )
    if ((_page & sec_mask) != 0)
        this->cur_size = secsz - (_page & sec_mask);

    if (this->cur_size > _len)
        this->cur_size = _len;

    this->cur_data = (uint8_t*)data;
    this->cycle_yield_to = 0;
}

/// @brief  init cycling wait last op finish
void        flash_cycling_wait_init(FlashCycling* this, flash_page_t page){
    flash_cycling_init(this, NULL, page, 1, 0);
}


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

    res  = self->select_addr(self, this->page);
    if (res != DEV_OK) return res;

    if (this->cur_size <= 0)
        return DEV_OK;

    res = (this->op)(self, this->cur_data, this->page, this->cur_size);
    if (!PT_SCHEDULE( AS_PTRESULT(res) ))
    if (res != DEV_OK) {
        this->len = 0; // оборвем flash_cycling
        return res;
    }
    this->len -= this->cur_size;
    this->page = ( this->page + this->cur_size) & this->adr_mask;
    this->cur_data += this->cur_size;
    this->cur_len  += this->cur_size;
    this->cur_size = (this->len < this->page_size) ? this->len : this->page_size;
    return res;
}



// цикл над многими страницами некоторой базовой операции.
// !!! пересечения page границы памяти ведет к заворачиванию адреса
PTResult flash_cycles(FLASH_Device* this){
    FlashState res = ptOK;
    FlashCycling* me = &this->cycx;
    struct pt* pt = &me->pt;

    trace_flash_cycles_twist();

    PT_BEGIN(pt)

    // read and aborted operations are only flush waits
    if ((me->len > 0) && (flash_to_noyields(me->cycle_to) > 0)) {

        // wait last flash operation finished. Use possible larger sector erase timeout - for sure
        // TODO: make startup timeout settings at cycx
        PT_SCHEDULE_RESULT_WHILE( pt, res, this->wait_ready(this, this->info->erase_ticks ) );
        if (!flash_is_status(res))
            PT_RESULT(pt, AS_PTRESULT(res) );
        if (!flash_is_ready(res))
            PT_RESULT(pt, AS_PTRESULT(res) );

    } //if ((me->len > 0) && (me->cycle_to > 0))

    me->cycle_yield = me->cycle_yield_to;
    while (me->len > 0) {

        if (flash_to_noyields(me->cycle_to) > 0) {
            PT_SCHEDULE_RESULT_WHILE( pt, res, this->wait_ready(this, me->cycle_to) );
            if (!flash_is_status(res))
                PT_RESULT(pt, AS_PTRESULT(res) );
            if (!flash_is_ready(res))
                PT_RESULT(pt, AS_PTRESULT(res) );
        }
        else {
            PT_SCHEDULE_RESULT_WHILE( pt, res, this->flush(this) );
        }

        res = flash_cycling_one(me, this);

        if (me->len > 0)
        if (me->cycle_yield > 0){
            // если включено расстановка рауз в длинном цикле, то через
            //  каждые cycle_yield_to делаю паузу текущему процессу
            me->cycle_yield--;
            if (me->cycle_yield <= 0 ){
                me->cycle_yield = me->cycle_yield_to;
                trace_flash_pause_twist();

                // отдам процессор другим ниткам
                PT_PAUSE(pt);
                //PT_YIELD(pt);
            }
        }
    }

    // read operations - without timeouts, they need completion, for results are ready
    //if ( (PT_SCHEDULE(res)) || (me->cycle_to == 0))
        PT_SCHEDULE_RESULT_WHILE( pt, res, this->flush(this) );

    PT_RESULT(pt, res);
    PT_END(pt)
}

void flash_cycles_abort(FLASH_Device* this){
    FlashCycling* me = &this->cycx;
    me->len = 0;
    me->cycle_to = 0;
}



// перед запуском цикл надо назначить
void flash_cycle_pages(FLASH_Device* this, void* data, flash_page_t page, unsigned len)
{
    FlashCycling* me = &this->cycx;

    ASSERT(this->info != NULL);
    me->adr_mask  = flash_size(this)-1;
    flash_cycling_init(me, data, page, len, this->info->page_size);
    PT_INIT(&me->pt);
}

void flash_cycle_sectors(FLASH_Device* this, void* data, flash_page_t page, unsigned len)
{
    FlashCycling* me = &this->cycx;

    me->adr_mask  = flash_size(this)-1;
    flash_cycling_init(me, data, page, len, flash_sec_size(this));
    PT_INIT(&me->pt);
}

void flash_cycle_op(FLASH_Device* this, flash_page_op op, flash_timeout_t to){
    FlashCycling* me = &this->cycx;

    me->op = op;
    me->cycle_to = to;
    me->cycle_yield_to = flash_to_yields(to);
    if (me->cycle_yield_to == 0) {
        if ( (to& FLASH_toMASK) == 0){
            // если таймаут не использует ожидания в мс, используется только спин-ожидание
            //  то циклическая операция \sa cycles(), старается делать паузы,
            // отдавая время другим задачам. дистанцию пауз - задаю общим количеством
            //      спинов
            unsigned spins = flash_to_spins(to);
            spins++; //нельзя делить на 0
            me->cycle_yield_to = (FLASH_toYIELD_LEN_SPINS/spins)+1;
        }
    }
}



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

// TODO: реализовать функцию через автомат состояний, с ожиданиями.
//virtual
PTResult flash_verify(FLASH_Device* this, flash_addr_t page, const void* src, unsigned len) {
    PTResult ok = ptOK;
    const uint8_t* ps = (const uint8_t*)src;
    // верифицирую строку
    //   сравнение буду вести кусками размера cunksz
    enum {chunk_limit = 32};
    unsigned cunksz = len;
    if ( cunksz > chunk_limit)
        cunksz = len/16;
    if ( cunksz > chunk_limit)
        cunksz = chunk_limit;

    for (unsigned x = 0; x < len; x = x+cunksz, ps += cunksz){
        uint8_t  chunk[chunk_limit];
        unsigned sz = cunksz;
        if (sz > (len - x) )
            sz = (len - x);
        //TODO: async read, need wait it
        ok = this->read(this, page+x, chunk, sz);

        if (ok != ptOK){
            return ok;
        }
        if (memcmp( chunk, ps, sz ) != 0){
            return ptNOK;
        }
    }
    return ptOK;
}

DevResult flash_bind_dummy(FLASH_Device* this){
    (void)this;
    return DEV_NOT_IMPLEMENTED;
}

PTResult flash_flush_dummy(FLASH_Device* this){
    (void)this;
    return ptOK;
}

PTResult flash_protect_sectors_dummy(FLASH_Device* this, flash_addr_t addr, unsigned len, bool onoff){
    (void)this; (void)addr; (void)len; (void)onoff;
    return ptOK;
}

DevResult flash_select_addr_dummy(FLASH_Device* this, flash_addr_t addr){
    (void)this; (void)addr;
    return DEV_OK;
}


void  flash_init(FLASH_Device* this){
    this->bind      = flash_bind_dummy;
    this->select_addr= flash_select_addr_dummy;
    this->flush     = flash_flush_dummy;
    this->protect_sectors = flash_protect_sectors_dummy;
    this->verify        = flash_verify;
    this->wait_ready    = flash_wait_ready;
    this->wait_ready_to = flash_wait_ready_to;
    this->status_cache  = 0;
    PT_INIT(&this->oppt);
    PT_INIT(&this->cycx.pt);
    //this->info      = NULL;
}




/// @brief проверяет занятость флеши:
///         - активен запрос ssp @sa this->flush
///         - активна нитка this->oppt
///         - активна нитка flash_cycles
/// @return ptWAITING - занята флеша
PTResult flash_is_wait(FLASH_Device* this){
    if ( PT_IS_NULL(&this->oppt) )
    if ( PT_IS_NULL(&this->cycx.pt) )
        return (this->flush)(this);

    return ptWAITING ;// ptYIELDED;
}

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

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

    PTResult ok = this->read(this, addr, dst, len);

    while (PT_SCHEDULE (ok))
        ok = flash_cycles(this);

    return AS_DEVRESULT(ok);
}


// неблокирующее чтение - включает ожидание готовности флеши и  flash_cycles, если требует драйвер
// @return ptYIELDED - операция в процессе запуска
//         ptWAITING - операция запущена в flash_cycles, и завершить её можно PT_SCHEDULE( flash_cycles(self) )
PTResult flash_wait_read(FLASH_Device* self, flash_addr_t addr, void* dst, unsigned len){
    DevResult ok;
    PTResult res;
    struct pt* pt = &self->oppt;

    PT_BEGIN(pt)

    ok = self->select_addr(self, addr);
    if (ok != DEV_OK)
        PT_RESULT(pt, AS_PTRESULT(ok) );

    // TODO: очень тяжелый код, вынужден работать через PT_SPAWN, но стоит перейти на единый стиль через flash_cycles.
    //  надо ускорять:
    //      a) можно проверить только что доступ не занят, и сразу отправить команду записи
    //      б) или сразу создавать задачу циклическую, и отдать запуск записи ей.

    while (PT_SCHEDULE( (ok=self->wait_ready(self, self->info->erase_ticks), ok) ))
        PT_YIELD(pt);

    if (!flash_is_status(ok))
        PT_RESULT(pt, AS_PTRESULT(ok) );
    if (!flash_is_ready(ok)){
         PT_RESULT(pt, AS_PTRESULT(DEV_TIME_OUT) );
    }

    res = (self->read)(self, addr, (void*)dst, len);
    if (! PT_SCHEDULE (res) )
        PT_RESULT(pt, res );

    PT_SCHEDULE_RESULT_WHILE( pt, res, flash_cycles(self) );
    PT_RESULT(pt, res);

    PT_END(pt)
}



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

PTResult  flash_read_pages(FLASH_Device* self, flash_addr_t page, void* dst, unsigned len
                                            , flash_page_op ask_page)
{

    DevResult ok = self->select_addr(self, page);
    if (ok != DEV_OK)
        return AS_PTRESULT(ok);

    if ( flash_in_single_page(self, page, len) ){
            //прерванный цикл только ожидает завершения SSP
            flash_cycles_abort(self);
            return ask_page(self, dst, page, len);
    }

    flash_cycle_pages(self, dst, page, len);
    flash_cycle_op(self, ask_page, 0);

    return flash_cycles(self);
}

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

{
    DevResult ok = self->select_addr(self, page);
    if (ok != DEV_OK)
        return AS_PTRESULT(ok);

    //прерванный цикл только ожидает завершения SSP
    flash_cycles_abort(self);
    return ask_page(self, dst, page, len);
}


PTResult flash_write_pages(FLASH_Device* self, flash_addr_t page, const void* src, unsigned len
                        , flash_page_op page_write)
{
    //SPIFlash_GenDevice* this = (SPIFlash_GenDevice*)self;

    DevResult ok;
    PTResult res;
    struct pt* pt = &self->oppt;

    PT_BEGIN(pt)

    ok = self->select_addr(self, page);
    if (ok != DEV_OK)
        PT_RESULT(pt, AS_PTRESULT(ok) );

    // TODO: очень тяжелый код, вынужден работать через PT_SPAWN, но стоит перейти на единый стиль через flash_cycles.
    //  надо ускорять:
    //      a) можно проверить только что доступ не занят, и сразу отправить команду записи
    //      б) или сразу создавать задачу циклическую, и отдать запуск записи ей.

    //PT_SCHEDULE_RESULT_WHILE( pt, ok, self->wait_ready(self, self->info->erase_ticks) );
    while (PT_SCHEDULE( (ok=self->wait_ready(self, self->info->erase_ticks), ok) ))
        PT_YIELD(pt);

    if (!flash_is_status(ok))
        PT_RESULT(pt, AS_PTRESULT(ok) );
    if (!flash_is_ready(ok)){
         PT_RESULT(pt, AS_PTRESULT(DEV_TIME_OUT) );
    }
    if (!flash_is_writable(ok)){
        // запись не разрешена
        PT_RESULT(pt, AS_PTRESULT(DEV_WRITE_DISABLED_ERR) );
    }

    if (flash_in_single_page(self, page, len)) {
        flash_cycles_abort(self);
        res = page_write(self, (void*)src, page, len);
        if (!PT_SCHEDULE(res))
            PT_RESULT(pt, res );
    }
    else {
        flash_cycle_pages(self, (void*)src, page, len);
        flash_cycle_op(self, page_write, self->info->burn_page_ticks);
    }

    PT_SCHEDULE_RESULT_WHILE( pt, res, flash_cycles(self) );
    PT_RESULT(pt, res);

    PT_END(pt)
}

PTResult flash_erase_sectors(FLASH_Device* self, flash_addr_t _from, unsigned _len,
                                    flash_page_op erase_sec)
{
    struct pt* pt = &self->oppt;
    DevResult ok;
    PTResult res;

    PT_BEGIN(pt)

    ok = self->select_addr(self, _from);
    if (ok != DEV_OK)
        PT_RESULT(pt, AS_PTRESULT(ok) );

    // TODO: очень тяжелый код, вынужден работать через PT_SPAWN, но стоит перейти на единый стиль через flash_cycles.
    PT_SCHEDULE_RESULT_WHILE( pt, ok, self->wait_ready(self, self->info->erase_ticks) );

    if (!flash_is_status(ok))
        PT_RESULT(pt, AS_PTRESULT(ok) );
    if (!flash_is_ready(ok))
        return AS_PTRESULT(DEV_TIME_OUT);
    if (!flash_is_writable(ok)){
        // запись не разрешена
        return AS_PTRESULT(DEV_WRITE_DISABLED_ERR);
    }

    if (_len == 1){
        flash_cycles_abort(self);
        res = erase_sec(self, NULL, _from, _len);
        if (!PT_SCHEDULE(res))
            PT_RESULT(pt, res );
    }
    else {
        flash_cycle_sectors(self, NULL, _from, _len);
        flash_cycle_op(self, erase_sec , self->info->erase_ticks);
    }

    PT_SCHEDULE_RESULT_WHILE( pt, res, flash_cycles(self) );
    PT_RESULT(pt, res);

    PT_END(pt)
}




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

PTResult flash_protect_all_sectors(FLASH_Device* this, bool onoff) {
    return this->protect_sectors(this,0, flash_size(this), onoff);
};
