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

#include <cassert>
#include <string.h>
#include "flash_hal.hpp"
#include <OsSync.h>
#include <system.h>
#include <trace_probes.h>



#ifndef trace_flash_pause
trace_need(flash_pause)
#endif
#ifndef trace_flash_rd
trace_need(flash_wr)
trace_need(flash_wrwe)
trace_need(flash_rd)
#endif



//-------------------------------------------------------------------------
//                          FLASH_Device
//--------------------------------------------------------------------------
//virtual
int  FLASH_Device::wait_ready(timeout_value_t waittime) __noexcept{
    if (ostimeout_waiting(&to_wait))
        return FLASH_Device::wait_ready(&to_wait);

    int status = state();
    busy_polled();

    if (is_ready(status)){
        busy_done();
        return status;
    }

    // spin wait for fast operatons
    if (waittime != toInfinite) {
        unsigned spin = to_spins(waittime);
        for (; is_busy(status) && (spin > 0) ; spin--){
            status = state();
            busy_polled();
        }
    }

    if (is_ready(status)){
        busy_done();
        return status;
    }

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

    ostimeout_init(&to_wait, to_ticks(waittime) );
    return FLASH_Device::wait_ready(&to_wait);
}

// ожидание готовности флешки веду поллингом с периодом toBUSYPoll
//virtual
int  FLASH_Device::wait_ready(os_timeout_t* to) __noexcept {
    int status = state();

    for (; is_busy(status); status = state() )
    {
        if (ostimeout_expired(to))
            break;
        if (PT_SCHEDULE( (status =sleep(toBUSYPoll), status)) )
            return status;
    }
    ostimeout_stop(to);
    busy_done();
    return status;
}

void FLASH_Device::assign_info(const Describe* x){
    _info       = x;
    _sec_size   = x->sec_size();
    _size       = x->chip_size();
    sec_width   = ctz(_sec_size);
}

//virtual
DevResult FLASH_Device::verify(unsigned page, const void* src, unsigned len) __noexcept {
    DevResult ok = DEV_OK;
    const u8* ps = (const u8*)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){
        u8  chunk[chunk_limit];
        unsigned sz = cunksz;
        if (sz > (len - x) )
            sz = (len - x);
        ok = read( page+x, chunk, sz);
        if (ok != DEV_OK){
            return ok;
        }
        if (memcmp( chunk, ps, sz ) != 0){
            return DEV_NOK;
        }
    }
    return DEV_OK;
}

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



//-------------------------------------------------------------------------
//                          SPIFlash_GenDevice
//--------------------------------------------------------------------------
SPIFlash_GenDevice::SPIFlash_GenDevice(dev_name name)
:inherited(name)
, SSPIO_Device()
, status_cache(0)
, CMD_SE(CMD_SE_64K)
{
}

//HAL_Device
//virtual
DevResult SPIFlash_GenDevice::init(){
    return DEV_OK;
}
//virtual
DevResult SPIFlash_GenDevice::deinit(){
    cs(io()->msCS_NONE);
    io()->release();
    return DEV_OK;
}

//virtual
DevResult SPIFlash_GenDevice::erase_all(){
    //return writeByte(CMD_BE);
    return io_ASDEVRESULT(ioByte(NULL, CMD_BE), 1);
}

//virtual
PTResult SPIFlash_GenDevice::erase_sectors(unsigned _from, unsigned _len){
    struct pt* pt = &oppt;
    DevResult ok;
    PTResult res;

    PT_BEGIN(pt)
    PT_SCHEDULE_RESULT_WHILE( pt, ok, wait_ready(info().erase_ticks) );
    if (!is_ready(ok))
        return AS_PTRESULT(DEV_TIME_OUT);
    if (!is_writable(ok)){
        // запись не разрешена
        return AS_PTRESULT(DEV_WRITE_DISABLED_ERR);
    }

    if (_len == 1)
        PT_RESULT(pt, erase_sec(NULL, _from, _len) );

    cycle_sectors(NULL, _from, _len);
    cycle_op((page_op)&SPIFlash_GenDevice::erase_sec, info().erase_ticks);
    PT_SCHEDULE_RESULT_WHILE( pt, res, cycles() );
    PT_RESULT(pt, res);

    PT_END(pt)
}


//virtual
DevResult SPIFlash_GenDevice::write_enable(bool onoff){
    cs_hold(false);
    //return writeByte((onoff)? CMD_WREN : CMD_WRDI);
    u8 op = (onoff)? CMD_WREN : CMD_WRDI;
    return io_ASDEVRESULT(ioByte(NULL, op), 1);
}

//virtual
PTResult SPIFlash_GenDevice::write(unsigned page, const void* src, unsigned len){
    DevResult ok;
    PTResult res;
    struct pt* pt = &oppt;

    PT_BEGIN(pt)

    PT_SCHEDULE_RESULT_WHILE( pt, ok, wait_ready(info().erase_ticks) );
    if (!is_ready(ok))
        return AS_PTRESULT(DEV_TIME_OUT);
    if (!is_writable(ok)){
        // запись не разрешена
        return AS_PTRESULT(DEV_WRITE_DISABLED_ERR);
    }

    if (is_single_page(page, len))
        PT_RESULT(pt, write_page((void*)src, page, len) );

    cycle_pages((void*)src, page, len);
    cycle_op((page_op)&SPIFlash_GenDevice::write_page, info().burn_page_ticks);
    PT_SCHEDULE_RESULT_WHILE( pt, res, cycles() );
    PT_RESULT(pt, res);

    PT_END(pt)
}

//virtual
DevResult SPIFlash_GenDevice::read(unsigned page, void* dst, unsigned len){
    unsigned page_size = info().page_size;
    // if Flash is per-byte writing (pagesie ==1), then suppose that it can
    //      continue read though all chip
    if (is_single_page(page, len)){
        return read_page(dst, page, len);
    }

    cycle_pages(dst, page, len);
    cycle_op((page_op)&SPIFlash_GenDevice::read_page, info().burn_page_ticks);
    return AS_DEVRESULT( cycles() );
}


PTResult FLASH_Device::cycles(){
    DevResult res;
    cycctx_t& me = cycx;
    struct pt* pt = &me.pt;
    PT_BEGIN(pt)
    me.cycle_yield = me.cycle_yield_to;
    while (me.len > 0) {
        PT_SCHEDULE_RESULT_WHILE( pt, res, wait_ready(me.cycle_to) );
        if (!is_ready(res))
            PT_RESULT(pt, res);

        res = me.cycle_one(this);

        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();
                process_post_pause();
                PT_YIELD(pt);
            }
        }
    }
    PT_RESULT(pt, ptOK);
    PT_END(pt)
}

void FLASH_Device::cycle_pages(void* data, page_t page, unsigned len)
{
    cycctx_t& me = cycx;

    me.adr_mask  = this->size()-1;
    me.init(data, page, len, info().page_size);
    PT_INIT(&me.pt);
}

void FLASH_Device::cycle_sectors(void* data, page_t page, unsigned len)
{
    cycctx_t& me = cycx;

    me.adr_mask  = this->size()-1;
    me.init(data, page, len, sec_size() );
    PT_INIT(&me.pt);
}

void FLASH_Device::cycle_op(page_op op, timeout_value_t to){
    cycctx_t& me = cycx;
    me.op = op;
    me.cycle_to = to;
    me.cycle_yield_to = to_yields(to);
    if (me.cycle_yield_to == 0) {
        if ( (to& toMASK) == 0){
            unsigned spins = to_spins(to);
            spins++; //нельзя делить на 0
            me.cycle_yield_to = (toYIELD_LEN_SPINS/spins)+1;
        }
    }
}

void  FLASH_Device::cycctx_t::init(void* data, page_t _page, unsigned _len
                                , unsigned secsz)
{
    unsigned sec_mask = secsz-1;

  	cur_size = secsz;
    len = _len;
    page_size = secsz;
    page      = _page;
    if ((page & sec_mask) != 0)
        cur_size = secsz - (page & sec_mask);
    if (cur_size > len)
        cur_size = len;

    cur_data = (u8*)data;
    cycle_yield_to = 0;
}

DevResult FLASH_Device::cycctx_t::cycle_one(FLASH_Device* self){
    DevResult res;

    res = (self->*op)(cur_data, page, cur_size);
    if (res != DEV_OK) {
        return res;
    }
    len -= cur_size;
    page = ( page + cur_size) & adr_mask;
    cur_data += cur_size;
    cur_size = (len < page_size) ? len : page_size;
    return res;
}

DevResult SPIFlash_GenDevice::write_page(void* src, addr_t page, unsigned len){
    TRACE_GUARD(wr, flash_wr);
    // flash burn starts after CS disabled
    cs_hold(false);
    busy_start();
    int writen = writeData(CMD_PP, page, (const void*)src, len);
    if (writen >= 0)
        return (writen == len)? DEV_OK:DEV_NOK;
    else
        return writen;
}

DevResult SPIFlash_GenDevice::read_page(void* dst, addr_t page, unsigned len){
    TRACE_GUARD(wr, flash_rd);
    int readen = readData(CMD_READ, page, dst, len);
    if (readen >= 0)
        return (readen == len)? DEV_OK:DEV_NOK;
    else
        return readen;
}

DevResult SPIFlash_GenDevice::erase_sec(void* dst, page_t page, unsigned len){
    cs_hold(false);
    int writen = write_cmdadress(CMD_SE, page);
    if (writen > 0)
        return DEV_OK;
    else
        return writen;
}

//virtual
PTResult SPIFlash_GenDevice::flush() {
    PTResult res = wait_ready(info().burn_page_ticks);
    if ( PT_SCHEDULE( res ))
        return res;

    int status = res;
    if (!is_ready(status))
        return AS_PTRESULT(DEV_TIME_OUT);
    if (!is_error(status))
        return ptOK;
    else
        return ptNOK;
};

//virtual
int  SPIFlash_GenDevice::state(){
    u8  buf[2];
    buf[0] = CMD_RDSR;
    buf[1] = 0xff;

    cs_hold(false);
    DevResult ok = ioData(buf, buf, 2);
    if (ok == 2){
        status_cache = buf[1] | sSTATUS;
        return status_cache;
    }
    return DEV_FAIL;
}


//-------------------------------------------------------------------------
//                          SPIFlash_WritableDevice
//--------------------------------------------------------------------------

#if (FLASHDEV_STYLE & FLASHDEV_FEATURE_WE_SURE)
#define FLASHDEV_WE_SURE   1
#endif

//virtual
DevResult SPIFlash_WritableDevice::write_enable(bool onoff){
    DevResult ok = inherited::write_enable(onoff);
    if (ok == DEV_OK)
        status_cache |= SR_WEL;
    return ok;
}

// state должен учитывать статус WE
//virtual
int       SPIFlash_WritableDevice::state(){
    unsigned WE_state = status_cache & SR_WEL;
    int ok = inherited::state();
    status_cache    |= WE_state;
    if (ok >=0){
        //ok          |= WE_state;
        //status_cache|= WE_state;
    }
    return ok;
}

//virtual
DevResult SPIFlash_WritableDevice::erase_all(){
    DevResult res;
    if (is_WEL()){
        res = enable_wr();
        if (res != DEV_OK)
            return res;
    }
    else
        return DEV_WRITE_DISABLED_ERR;
    return io_ASDEVRESULT(ioByte(NULL, CMD_BE), 1); //writeByte(CMD_BE);
}

//virtual
PTResult SPIFlash_WritableDevice::erase_sectors(unsigned _from, unsigned _len){
    struct pt* pt = &oppt;
    DevResult ok;
    PTResult res;

    PT_BEGIN(pt)

    if (!is_WEL())
        return AS_PTRESULT(DEV_WRITE_DISABLED_ERR);

    PT_SCHEDULE_RESULT_WHILE( pt, ok, wait_ready(info().erase_ticks) );
    if (!is_ready(ok))
        return AS_PTRESULT(DEV_TIME_OUT);

    if (_len == 1)
        PT_RESULT(pt, erase_sec(NULL, _from, _len) );

    cycle_sectors(NULL, _from, _len);
    cycle_op((page_op)&SPIFlash_GenDevice::erase_sec, info().erase_ticks);
    PT_SCHEDULE_RESULT_WHILE( pt, res, cycles() );
    PT_RESULT(pt, res);

    PT_END(pt)
}

//virtual
PTResult SPIFlash_WritableDevice::write(unsigned page, const void* src, unsigned len){
    DevResult ok;
    PTResult res;
    struct pt* pt = &oppt;

    PT_BEGIN(pt)

    if (!is_WEL())
        PT_RESULT(pt, AS_PTRESULT(DEV_WRITE_DISABLED_ERR) );

    PT_SCHEDULE_RESULT_WHILE( pt, ok, wait_ready(info().erase_ticks) );
    if (!is_ready(ok))
        return AS_PTRESULT(DEV_TIME_OUT);

    if (is_single_page(page, len))
        PT_RESULT(pt, write_page((void*)src, page, len) );

    cycle_pages((void*)src, page, len);
    cycle_op((page_op)&SPIFlash_WritableDevice::write_page, info().burn_page_ticks);
    PT_SCHEDULE_RESULT_WHILE( pt, res, cycles() );
    PT_RESULT(pt, res);

    PT_END(pt)
}

DevResult SPIFlash_WritableDevice::enable_wr(){
#if FLASHDEV_WE_SURE
    cs_hold(false);
    if (ioByte(NULL, CMD_WREN) == 1){
        trace_flash_wrwe_on();
        int ok = state();
        trace_flash_wrwe_off();
        if ( is_writable(ok) && is_ready(ok) )
            return DEV_OK;
    }
    return DEV_NOK;
#else
    TRACE_GUARD(we, flash_wrwe);
    cs_hold(false);
    return io_ASDEVRESULT(ioByte(NULL, CMD_WREN), 1); //writeByte(CMD_WREN);
#endif
}

DevResult SPIFlash_WritableDevice::check_wr( int ioresult ){
    DevResult res = io_ASDEVRESULT(ioresult, 1); //writeByte(CMD_WREN);
#if FLASHDEV_WE_SURE
    if (res == DEV_OK){
        int ok = state();
        if ( is_writable(ok) )
            // операция записи флеши должна снять флаг WE
            return DEV_NOK;
    }
#endif
    return res;
}

DevResult SPIFlash_WritableDevice::write_page(void* src, addr_t page, unsigned len){
    if (!is_WEL())
        return DEV_WRITE_DISABLED_ERR;

    TRACE_GUARD(wr, flash_wr);

    if (enable_wr() != DEV_OK)
        return DEV_NOK;

    cs_hold(false);
    busy_start();
    int writen = writeData(CMD_PP, page, (const void*)src, len);
    return check_wr(writen);
}

DevResult SPIFlash_WritableDevice::erase_sec(void* dst, page_t page, unsigned len){
    if (!is_WEL())
        return DEV_WRITE_DISABLED_ERR;

    TRACE_GUARD(wr, flash_rd);

    if (enable_wr() != DEV_OK)
        return DEV_NOK;

    cs_hold(false);
    busy_start();
    int writen = write_cmdadress(CMD_SE, page);
    return check_wr(writen);
}





//-------------------------------------------------------------------------
//                          Flash_Bank
//--------------------------------------------------------------------------
Flash_Bank::Flash_Bank(dev_name name)
: inherited(name)
, _io(NULL)
, _banks(NULL)
, banks_num(0)
, bank_width(0)
{
}

//virtual
DevResult Flash_Bank::init(){
    if (io() == NULL)
        return DEV_NOT_IMPLEMENTED;
    return io()->init();
}

//virtual
int Flash_Bank::deinit(){
    if (io() == NULL)
        return DEV_NOT_IMPLEMENTED;
    return io()->deinit();
}

// \arg banks - Zstring chip banks
DevResult Flash_Bank::connect(flash_t* _flash, bank_list bankset){
    _io =  _flash;
    banks(bankset);
    return DEV_OK;
}

void      Flash_Bank::banks(bank_list bankset){
    unsigned num = strlen((const char*)bankset); //, 16);
    if ((num > 0) && (num < 16)){
        _banks = bankset;
        banks_num = num;
    }
}

#define ASSIGN_FIELD(dst, src, f)       {dst.f = src.f;}

//virtual
DevResult Flash_Bank::bind() __noexcept{
    DevResult ok;
    for (int b = 0; b < banks_num; b++){
        ok = io()->select( _banks[b] );
        ok = io()->bind();
        if (ok != DEV_OK)
            return ok;
    }
    ok = io()->release();
    const Describe& io_info = io()->info();
    bank_info = io_info;
    bank_info.nb_sectors *= banks_num;
    assign_info(&bank_info);
    bank_width = ctz(io()->size());
    return DEV_OK;
}

//virtual
DevResult Flash_Bank::write_enable(bool onoff) __noexcept {
    DevResult ok;
    for (int b = 0; b < banks_num; b++){
        ok = io()->select( _banks[b] );
        if (ok < 0) return ok;
        ok = io()->write_enable(onoff);
        if (ok < 0) return ok;
    }
    ok = io()->release();
    return DEV_OK;
}

//virtual
DevResult Flash_Bank::erase_all()  __noexcept {
    if ( !is_ready(state()) )
        return DEV_TIME_OUT;

    DevResult ok;
    for (int b = 0; b < banks_num; b++){
        ok = io()->select( _banks[b] );
        if (ok < 0) return ok;
        ok = io()->erase_all();
        if (ok < 0) return ok;
    }

    return DEV_OK;
}

//virtual
PTResult Flash_Bank::erase_sectors(unsigned from, unsigned len) __noexcept {
    DevResult ok;
    PTResult res;
    struct pt* pt = &oppt;

    PT_BEGIN(pt)

    PT_SCHEDULE_RESULT_WHILE( pt, ok, wait_ready(info().erase_ticks) );
    if (!is_ready(ok))
        return AS_PTRESULT(DEV_TIME_OUT);
    if (!is_writable(ok)){
        // запись не разрешена
        return AS_PTRESULT(DEV_WRITE_DISABLED_ERR);
    }

    if (len == 1){
        PT_SPAWN_RESULT( pt, ( &(io()->oppt) ), res, erase_op(NULL, from, len) );
        PT_RESULT(pt, res );
    }

    cycle_banks( NULL, from, len, &Flash_Bank::erase_op );
    PT_SCHEDULE_RESULT_WHILE( pt, res, cycles() );
    PT_RESULT(pt, res);

    PT_END(pt)
}

PTResult Flash_Bank::erase_op(void* data, addr_t page, unsigned size){
    return io()->erase_sectors(page, size);
}

//virtual
PTResult Flash_Bank::protect_sectors(unsigned page, unsigned len, bool onoff) __noexcept {
    PTResult res;
    struct pt* pt = &oppt;

    PT_BEGIN(pt)

    cycle_banks( NULL , page, len
              , (onoff)? &Flash_Bank::protect_op : &Flash_Bank::unprotect_op
              );
    PT_SCHEDULE_RESULT_WHILE( pt, res, cycles() );
    PT_RESULT(pt, res);

    PT_END(pt)
}

//virtual
PTResult Flash_Bank::write(unsigned page, const void* src, unsigned len) __noexcept{
    PTResult res;
    struct pt* pt = &oppt;

    PT_BEGIN(pt)
    if ((len == 1) || is_single_bank(page, len)){
        res = select_bank_page(page);
        if (res != DEV_OK)
            PT_RESULT(pt, res);

        PT_SCHEDULE_RESULT_WHILE( pt, res, io()->write(page, src, len) );
        PT_RESULT(pt, res);
    }

    cycle_banks((void*)src, page, len, &Flash_Bank::write_op );
    PT_SCHEDULE_RESULT_WHILE( pt, res, cycles() );
    PT_RESULT(pt, res);

    PT_END(pt)
}

//virtual
DevResult Flash_Bank::read(unsigned page, void* dst, unsigned len) __noexcept{
    if ((len == 1) || is_single_bank(page, len)){
        DevResult res = select_bank_page(page);
        if (res == DEV_OK)
            res = io()->read(page, dst, len);
        return res;
    }

    cycle_banks(dst, page, len, &Flash_Bank::read_op );
    PTResult res = cycles();
    assert(!PT_SCHEDULE(res));
    return AS_DEVRESULT(res);
}

DevResult Flash_Bank::verify(unsigned page, const void* src, unsigned len) __noexcept
{
    if ((len == 1) || is_single_bank(page, len)){
        DevResult res = select_bank_page(page);
        if (res == DEV_OK)
            res = io()->verify(page, src, len);
        return res;
    }

    cycle_banks((void*)src, page, len, &Flash_Bank::verify_op );
    PTResult res = cycles();
    assert(!PT_SCHEDULE(res));
    return AS_DEVRESULT(res);
}

//virtual
PTResult Flash_Bank::flush() __noexcept {
    int status = wait_ready(info().burn_page_ticks);
    if ( PT_SCHEDULE( (PTResult)status ))
        return status;

    if (!is_ready(status))
        return AS_PTRESULT(DEV_TIME_OUT);
    if (!is_error(status))
        return ptOK;
    else
        return ptNOK;
}

//virtual
int  Flash_Bank::state() __noexcept {
    int res = 0;
    for (int b = 0; b < banks_num; b++){
        DevResult ok = io()->select( _banks[b] );
        ok = io()->state();
        if (ok < 0)
            break;
        res |= ok;
    }
    io()->release();
    return res;
}

// нужно для ожидания статуса отдельного банка
int  Flash_Bank::state_bank(unsigned page){
    int ok = select_bank_page(page);
    if (ok == DEV_OK)
        return io()->state();
    return ok;
}

int  Flash_Bank::wait_bank(unsigned page, timeout_value_t to){
    int ok = select_bank_page(page);
    if (ok == DEV_OK)
        return io()->wait_ready(to);
    return ok;
}

Flash_Bank::bank_t Flash_Bank::sec_bank(addr_t sec) const{
    return ( sec >> (bank_width-sec_width) );
}

Flash_Bank::bank_t Flash_Bank::addr_bank(addr_t page) const{
    assert(bank_width < 32);
    return page >> bank_width;
}


DevResult Flash_Bank::select_bank_page(addr_t page){
    unsigned b = addr_bank(page);
    if (b < banks_num)
        return io()->select( _banks[b] );
    else
        return DEV_OUT_OF_SPACE_ERR;
}

void Flash_Bank::cycle_banks(void* data
        , addr_t _page, unsigned _len
        , bank_op op)
{
    cycctx_t& me = cycx;

    unsigned bank_size = io()->size();
    assert(bank_size > 0);

    me.adr_mask  = this->size()-1;
    me.init(data, _page, _len, bank_size );
    me.op = (page_op)op;
    PT_INIT(&me.pt);
}

PTResult Flash_Bank::cycles(){
    //PTResult res;
    DevResult ok;
    cycctx_t& me = cycx;
    struct pt* pt = &me.pt;
    PT_BEGIN(pt)
    while (me.len > 0) {
        ok = select_bank_page(me.page);
        if (ok != DEV_OK)
            PT_RESULT(pt, AS_DEVRESULT(ok));

        PT_SCHEDULE_RESULT_WHILE( pt, ok, io()->wait_ready(info().erase_ticks) );
        if (!is_ready(ok))
            PT_RESULT(pt, AS_PTRESULT(DEV_TIME_OUT));

        PT_SCHEDULE_RESULT_WHILE( pt, ok, me.cycle_one(this) );
        if (ok != DEV_OK)
            PT_RESULT(pt, AS_PTRESULT(ok));
    }
    io()->release();
    PT_RESULT(pt, ptOK);
    PT_END(pt)
}

PTResult Flash_Bank::write_op(void* data, addr_t page, unsigned len){
    return io()->write(page, (const void*)data, len);
}
PTResult Flash_Bank::read_op(void* data, addr_t page, unsigned len){
    return io()->read(page, data, len);
}

PTResult Flash_Bank::verify_op(void* data, addr_t page, unsigned len){
    return io()->verify(page, data, len);
}


PTResult Flash_Bank::protect_op(void* data, addr_t page, unsigned len){
    return io()->protect_sectors(page, len, true );
}

PTResult Flash_Bank::unprotect_op(void* data, addr_t page, unsigned len){
    return io()->protect_sectors(page, len, false );
}

