/*
 * flash_tests.cpp
 *
 *  Created on: 13 февр. 2019 г.
 *      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. *
 * ------------------------------------------------------------------------
 * этот код расчитан на исполнение с ОС contiki
 */
#include "../utils/flash_tests.hpp"

#include <OsTime.h>

extern "C"{
#include <sys/rtimer.h>
}


pt  flash_test_pt;
static rtimer_clock_t   flash_time_start;
static rtimer_clock_t   flash_time_end;

PTResult    flash_inspect(flash_props_t& descr){
    struct pt* pt = &flash_test_pt;
    flash_props_t::mem_t* mem = descr.mem;
    //bool        isok;
    DevResult   ok;
    PTResult    res;
    unsigned long writen;
    static unsigned long addr;
    static unsigned long len;
    //const uint64_t page_pattern = 0x0807060504030201ull;
    const uint32_t page_pattern = 0x04030201ull;
    enum{ ADR_LIMIT = (1<<24)
        , SIZE_MIN  = 1<< 9
        , SEC_MIN   = 8
        , PAGE_MAX  = 1<<12
    };

    PT_BEGIN(pt)

    assert(descr.mem != NULL);
    descr.clear();

    ok = mem->write_enable(true);
    if (ok != DEV_OK)
        PT_RESULT(pt, ok);

    ok = mem->state();
    /*
    if (mem->is_protected(ok)){
        // bank has protected sectors.
        ok = mem->protect_sectors(false);
        if (ok != DEV_OK)
            PT_RESULT(pt, ok);
    }
    */

#if 1
    // exemine erase_all, size, and sec_size

    // exemine erase all time
    flash_time_start = RTIMER_NOW();
    ok = descr.mem->erase_all();
    if (ok != DEV_OK)
        PT_RESULT(pt, ok);

    PT_SCHEDULE_RESULT_WHILE( pt, ok,
            mem->wait_ready( mem->info().erase_all_ticks ) );
    flash_time_end = RTIMER_NOW();
    if (ok < 0)
        PT_RESULT(pt, AS_PTRESULT(ok) );

    if (mem->is_error(ok))
        PT_RESULT(pt, ptFAIL );

    if (!mem->is_writable(ok)){
        mem->write_enable(true);
    }

    descr.erase_all_ticks = flash_time_end - flash_time_start;

    // now exemine all size and sec_size;
    // do it by consequtive writing markers at adress n^2

    // fill for minimum chip size, use it for sector size inspect
    for( descr.size = SEC_MIN; descr.size < SIZE_MIN; descr.size = descr.size << 1){
        ok = mem->write(descr.size, &descr.size, sizeof(descr.size));
        if (ok != DEV_OK)
            return AS_PTRESULT(ok);

        PT_SCHEDULE_RESULT_WHILE( pt, ok,
                mem->wait_ready(mem->info().burn_page_ticks) );
        if (!mem->is_ready(ok))
            return ptNOK;

        ok = mem->verify(descr.size, &descr.size, sizeof(descr.size) );
        if (ok != DEV_OK)
            return AS_PTRESULT(ok);
    }

    for( descr.size = descr.size << 1
            ; descr.size < ADR_LIMIT
            ; descr.size = descr.size << 1)
    {
        //erase sector 0
        PT_INIT(&mem->oppt);
        flash_time_start = RTIMER_NOW();
        PT_SCHEDULE_RESULT_WHILE( pt, res,  mem->erase_sectors(0, 1 ) );
        if (res != ptOK)
            return AS_PTRESULT(res);

        PT_SCHEDULE_RESULT_WHILE( pt, ok,
                mem->wait_ready(mem->info().erase_ticks) );
        if (mem->is_error(ok))
            PT_RESULT(pt, ptFAIL );
        flash_time_end = RTIMER_NOW();
        {
            unsigned time = flash_time_end - flash_time_start;
            if (descr.erase_sec_ticks < time)
                descr.erase_sec_ticks = time;
        }

        //write sector mark
        PT_INIT(&mem->oppt);
        flash_time_start = RTIMER_NOW();
        PT_SCHEDULE_RESULT_WHILE( pt, res,
                mem->write(descr.size, &descr.size, sizeof(descr.size) )
                );
        if (res != ptOK)
            return AS_PTRESULT(res);

        PT_SCHEDULE_RESULT_WHILE( pt, ok,
                mem->wait_ready(mem->info().burn_page_ticks) );
        if (!mem->is_ready(ok))
            return ptNOK;
        flash_time_end = RTIMER_NOW();
        {
            unsigned time = flash_time_end - flash_time_start;
            if (descr.burn_ticks < time)
                descr.burn_ticks = time;
        }

        // check that written mark not overrides memory
        ok = mem->read(0, &writen, sizeof(descr.size) );
        if (ok != DEV_OK)
            return AS_PTRESULT(ok);

        if (writen == descr.size){
            // write over memory size
            break;
        }
    }

    // memory filled with adress markers? and sector 0 erased
    // lets check sector boarder
    for( descr.sec_size = SEC_MIN
            ; descr.sec_size < ADR_LIMIT
            ; descr.sec_size = descr.sec_size << 1)
    {
        // check that written mark not overrides memory
        ok = mem->read(descr.sec_size, &writen, sizeof(descr.sec_size) );
        if (ok != DEV_OK)
            return AS_PTRESULT(ok);

        if (writen != ~0u){
            if (writen == descr.sec_size){
                // sector board is found
                break;
            }
            // strange! some dirty sector
            PT_RESULT(pt, ptNOK);
        }
    }
#else
    descr.sec_size = mem->info().sec_size();
    descr.size     = mem->size();
#endif

    // prepage sector 0
    PT_INIT(&mem->oppt);
    PT_SCHEDULE_RESULT_WHILE( pt, res,  mem->erase_sectors(0, 1 ) );
    if (res != ptOK)
        return AS_PTRESULT(res);

    PT_SCHEDULE_RESULT_WHILE( pt, ok,
            mem->wait_ready(mem->info().erase_ticks) );
    if (!mem->is_ready(ok))
        return ptNOK;

    // now inspect write page boundary
    // do it writes pattern 4 bytes offseted on 2 bytes, so that it should
    //      overrun page, and last 2 bytes will are at page start

    for( descr.page_size = SEC_MIN
            ; descr.page_size < descr.sec_size
            ; descr.page_size = descr.page_size << 1)
    {
        //write sector mark
        ok = mem->write_page((void*)&page_pattern
                        , descr.page_size-(sizeof(page_pattern)/2)
                        , sizeof(page_pattern)
                        );
        if (ok != ptOK)
            return AS_PTRESULT(ok);
        PT_SCHEDULE_RESULT_WHILE( pt, ok,
                mem->wait_ready(mem->info().burn_page_ticks) );
        if (!mem->is_ready(ok))
            return ptNOK;

        // check that written mark not overrides memory
        ok = mem->read(0, &writen, sizeof(writen) );
        if (ok != DEV_OK)
            return AS_PTRESULT(ok);

        if (writen != ~0u){
            // page board is found
            break;
        }
    }

    // lets inspect page after fill it by patterns
    for( addr = SEC_MIN; addr < descr.page_size; addr = addr << 1){
        ok = mem->read(addr - (sizeof(page_pattern)/2), &writen, sizeof(writen) );
        if (ok != DEV_OK)
            return AS_PTRESULT(ok);
        if (writen !=page_pattern){
            //looks page write crushes page content
            descr.write_size = addr;
            break;
        }
    }

    enum {
        LEN_STEP = 4
        , LEN_MIN = 8
        , LEN_MAX = 32
    };
    for( addr = LEN_MIN+descr.page_size, len = LEN_MIN
            ; len < LEN_MAX
            ; len += LEN_STEP, addr += descr.page_size  )
    {
        //write pattern for test
        {
            char    test_buf[LEN_MAX];
            for (int i = 0; i < LEN_MAX; i++) test_buf[i] = i;
            ok = mem->write_page((void*)test_buf, addr, len);
        }
        if (ok != ptOK)
            return AS_PTRESULT(ok);
        PT_SCHEDULE_RESULT_WHILE( pt, ok,
                mem->wait_ready(mem->info().burn_page_ticks) );
        if (!mem->is_ready(ok))
            return ptNOK;

        // check that pattern written good
        {
            char    test_buf[LEN_MAX];
            ok = mem->read(addr, test_buf, len );
            for (int i = 0; i < LEN_MAX; i++){
                if (test_buf[i] != i){
                    //ok = DEV_NOK;
                    //break;
                    descr.write_size |= 1<<(len/LEN_STEP);
										break;
                }
            }
        }
        if (ok != DEV_OK)
            return AS_PTRESULT(ok);
    }


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


