//=============================================================================
//
//      nand.c
//
//      Main application interface for the eCos NAND flash library
//
//=============================================================================
// ####ECOSGPLCOPYRIGHTBEGIN####                                            
// -------------------------------------------                              
// This file is part of eCos, the Embedded Configurable Operating System.   
// Copyright (C) 2009 eCosCentric Limited.
//
// eCos is free software; you can redistribute it and/or modify it under    
// the terms of the GNU General Public License as published by the Free     
// Software Foundation; either version 2 or (at your option) any later      
// version.                                                                 
//
// eCos is distributed in the hope that it will be useful, but WITHOUT      
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or    
// FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License    
// for more details.                                                        
//
// You should have received a copy of the GNU General Public License        
// along with eCos; if not, write to the Free Software Foundation, Inc.,    
// 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.            
//
// As a special exception, if other files instantiate templates or use      
// macros or inline functions from this file, or you compile this file      
// and link it with other works to produce a work based on this file,       
// this file does not by itself cause the resulting work to be covered by   
// the GNU General Public License. However the source code for this file    
// must still be made available in accordance with section (3) of the GNU   
// General Public License v2.                                               
//
// This exception does not invalidate any other reasons why a work based    
// on this file might be covered by the GNU General Public License.         
// -------------------------------------------                              
// ####ECOSGPLCOPYRIGHTEND####                                              
//=============================================================================
//#####DESCRIPTIONBEGIN####
//
// Author(s):   wry
// Date:        2009-03-02
//
//####DESCRIPTIONEND####
//=============================================================================

#include <cyg/hal/drv_api.h> // mutexes
#include <cyg/infra/diag.h>
#include <cyg/infra/cyg_ass.h>

#include <cyg/nand/nand.h>
#include <cyg/nand/nand_device.h>
#include <cyg/nand/nand_devtab.h>
#define CYG_NAND_ALLOW_INTERNALS
#include <cyg/nand/nand_internal.h>
#include <cyg/nand/util.h>
#include "nand_bbt.h"
#include CYGBLD_ISO_ERRNO_CODES_HEADER
#include <string.h>

/* ============================================================ */
// Timing instrumentation hooks; or "where is all the time going?"
// timetag_to_csv.pl will turn gdb output ("p tagslist") into CSV,
// for ease of analysis.

#ifdef CYGSEM_IO_NAND_INSTRUMENT_TIMING
# ifndef HAL_CLOCK_READ
#  error HAL_CLOCK_READ required
# endif
# define TAGSIZE 1024
cyg_uint32 tagslist[TAGSIZE];
cyg_uint32 tags_next;
static cyg_int64        rtc_resolution[] = CYGNUM_KERNEL_COUNTERS_RTC_RESOLUTION;
static cyg_int64        rtc_period = CYGNUM_KERNEL_COUNTERS_RTC_PERIOD;

# define TAG(_x) do {                           \
    tagslist[tags_next++] = _x;                 \
    if (tags_next >= TAGSIZE) tags_next = 0;    \
} while(0)

# define TIMETAG() do {     \
    cyg_uint64 tick;        \
    cyg_uint32 haltick;     \
    tick=cyg_current_time();\
    HAL_CLOCK_READ(&haltick);\
    TAG(__LINE__);          \
    TAG(tick&0xFFFFFFFF);   \
    TAG(haltick);           \
} while(0)

# define TIMETAG_INIT() do {        \
    int _i;                         \
    for (_i=0; _i<TAGSIZE; _i++)    \
        tagslist[_i]=0;             \
    tags_next = 0;                  \
    cyg_uint32 _tt, _tu;            \
    HAL_CLOCK_READ(&_tt);           \
    _tu = _tt;                      \
    while (_tt == _tu)              \
        HAL_CLOCK_READ(&_tu);       \
    TIMETAG();                      \
    (void) rtc_period;              \
    (void) rtc_resolution;          \
} while(0)

#else // ! CYGSEM_IO_NAND_INSTRUMENT_TIMING

# define TIMETAG_INIT() CYG_EMPTY_STATEMENT
# define TIMETAG() CYG_EMPTY_STATEMENT

#endif

/* ============================================================ */

/* We have a global ("devinit") lock, protects the nanddevtab and all 
 * writes to device->isInited.
 *
 * Each device also has its own lock, which protects the rest of
 * the structure, and the hardware itself.
 *
 * Rules:
 * 1. Nothing can use a device until it has been fully initialised 
 * (i.e. they must check dev->isInited, which is not set until
 * initialisation is complete);
 * 2. All calls to a device which has been initialised must acquire
 * the device's lock, to prevent multiple access;
 * 3. A repeated call to initialise a device is a safe no-op (under the
 * global lock, see that isInited is set, so do nothing);
 * 4. It is not possible for multiple threads to simultaneously 
 * initialise the same device [via the lookup function]
 * (they are protected by the global lock - the second blocks
 * until the first has completed, at which point the second thread 
 * sees that the device has been inited and falls out into a no-op).
 *
 * Therefore, an operation in devinit which uses the chip need not
 * assert the per-device lock, as nothing else can use the chip until
 * it has completed and set isInited to 1.
 */

static cyg_drv_mutex_t devinit_lock;

#define LOCK_devinit() cyg_drv_mutex_lock(&devinit_lock)
#define UNLOCK_devinit() cyg_drv_mutex_unlock(&devinit_lock)

/* Per-device lock.
 * NB that if multiple devices might interact (e.g. sharing a CPLD),
 * their drivers must act in concert to prevent this, usually at the 
 * platform level. */
#define LOCK_DEV(dev)   cyg_drv_mutex_lock(&dev->devlock)
#define UNLOCK_DEV(dev) cyg_drv_mutex_unlock(&dev->devlock)

/* ============================================================ */
/* Initialisation and lookup */

__externC void cyg_nand_bbt_initx(void);
static cyg_nand_printf nand_default_pf;

// This is called only by the C++ static constructor. Applications
// never need to call this themselves.
__externC void cyg_nand_initx(cyg_nand_printf pf)
{
    if (pf) CYG_CHECK_FUNC_PTRC(pf);
    cyg_drv_mutex_init(&devinit_lock);
    cyg_nand_bbt_initx();
    nand_default_pf = pf;
}

static void announce_partitions(cyg_nand_device *dev)
{
    int i, live_partitions = 0;
    for (i=0; i<CYGNUM_NAND_MAX_PARTITIONS; i++)
        if (dev->partition[i].dev) ++live_partitions;
    if (live_partitions)
        NAND_CHATTER(1,dev, "%s: %u partition%c configured\n", dev->devname, live_partitions, live_partitions==1 ? ' ' : 's' );
    else
        NAND_CHATTER(1,dev, "%s: NO partitions configured!\n", dev->devname); // hope they know what they're doing.
}

__externC
cyg_nand_partition* cyg_nand_get_partition(cyg_nand_device *dev, unsigned partno)
{
    if ((partno < 0) || (partno >= CYGNUM_NAND_MAX_PARTITIONS))
        return NULL;
    if (!dev->plf_update_partitions_called) {
        dev->plf_update_partitions_called = true; // Set this before the actual call, to kill a chicken-and-egg recursion where partition 0 needs to be read in order to determine the geometry of the other partitions.
        if (dev->plf_update_partitions != NULL) {
            int st = dev->plf_update_partitions(dev);
            if (st != 0) {
                NAND_ERROR(dev, "%s: Partition table update failed (code %d)\n", dev->devname, st);
                // Well, we tried.
            }
        }
        announce_partitions(dev);
    }
    LOCK_DEV(dev);
    cyg_nand_partition *rv = &(dev->partition[partno]);
    UNLOCK_DEV(dev);
    if (!rv->dev) return NULL; /* partition inactive */
    return rv;
}

__externC
int cyg_nand_lookup(const char *devname, cyg_nand_device **dev_o)
{
    int rv = -ENOENT;
    if (dev_o) {
        CYG_CHECK_DATA_PTRC(dev_o);
        *dev_o = 0;
    }
    if (!devname) return -EINVAL;

    LOCK_devinit(); // ++++++++++++++++++++++++++++++++++++++++++++
    cyg_nand_device *dev;
    for (dev = &cyg_nanddevtab[0]; dev != &cyg_nanddevtab_end; dev++) {
        if (0==strcmp(devname, dev->devname)) {
            rv = 0;
            break;
        }
    }
    if (!rv) {
        if (!dev->is_inited) {
            int i;

            if (dev->version != 2) {
                NAND_ERROR(dev, "Device %s declares incompatible version %d (expected 2)", devname, dev->version);
                goto done;
            }
            CYG_CHECK_DATA_PTRC(dev->fns);
            CYG_CHECK_FUNC_PTRC(dev->fns->devinit);

            dev->pf = nand_default_pf;
            for (i=0; i<CYGNUM_NAND_MAX_PARTITIONS; i++)
                dev->partition[i].dev = 0;
#ifdef CYGSEM_IO_NAND_USE_BBT
            dev->bbt.data = 0; // Paranoia, ensure devinit sets up
#endif

            rv = dev->fns->devinit(dev);

            if (rv) {
                NAND_ERROR(dev,"Could not initialise NAND device \"%s\": code %d\n", devname, rv);
                goto done;
            }

            // Now check that we have everything we need
            CYG_CHECK_FUNC_PTRC(dev->fns->read_begin);
            CYG_CHECK_FUNC_PTRC(dev->fns->read_stride);
            CYG_CHECK_FUNC_PTRC(dev->fns->read_finish);
            CYG_CHECK_FUNC_PTRC(dev->fns->write_begin);
            CYG_CHECK_FUNC_PTRC(dev->fns->write_stride);
            CYG_CHECK_FUNC_PTRC(dev->fns->write_finish);

            CYG_CHECK_FUNC_PTRC(dev->fns->erase_block);
            CYG_CHECK_FUNC_PTRC(dev->fns->is_factory_bad);

#ifdef CYGSEM_IO_NAND_USE_BBT
            CYG_CHECK_DATA_PTRC(dev->bbt.data);
#endif
            CYG_CHECK_DATA_PTRC(dev->ecc);
            CYG_CHECK_DATA_PTRC(dev->oob);
            if (!dev->chipsize_log ||
                    !dev->blockcount_bits ||
                    !dev->block_page_bits ||
                    !dev->spare_per_page ||
                    !dev->page_bits ||
#ifdef CYGSEM_IO_NAND_USE_BBT
                    !dev->bbt.data ||
#endif
                    !dev->ecc ||
                    !dev->oob) {
                NAND_ERROR(dev,"BUG: NAND driver devinit did not fill in all required fields - disabling device\n");
                rv = -ENOSYS;
                goto done;
            }

#ifdef CYGSEM_IO_NAND_USE_BBT
            if (dev->bbt.datasize < (1 << (dev->blockcount_bits-2)) ) {
                NAND_ERROR(dev,"BUG: NAND driver declared bbt.data_size isn't big enough (got %lu, want %u) - disabling device\n", (unsigned long) dev->bbt.datasize, (1 << (dev->blockcount_bits-2)));
                rv = -ENOSYS;
                goto done;
            }
#endif

            if ( dev->oob->ecc_size != CYG_NAND_ECCPERPAGE(dev) ) {
                NAND_ERROR(dev,"BUG: NAND driver has inconsistent ECC size declaration (oob says %d, ecc says %d) - disabling device\n", dev->oob->ecc_size, CYG_NAND_ECCPERPAGE(dev));
                rv = -ENOSYS;
                goto done;
            }

            // NOW we are ready to read from the device !

#ifdef CYGSEM_IO_NAND_USE_BBT
            rv = cyg_nand_bbti_find_tables(dev);
            if (rv == -ENOENT) {
                NAND_CHATTER(1,dev, "Creating initial bad block table on device %s\n", devname);
                rv = cyg_nand_bbti_build_tables(dev);
            }
            if (rv != 0) {
                NAND_ERROR(dev,"Cannot find or build BBT (%d)\n", -rv);
                goto done;
            }
#endif
            cyg_drv_mutex_init(&dev->devlock);
            dev->plf_update_partitions_called = false;
            dev->is_inited = 1;
            announce_partitions(dev);
        }
        if (dev_o) *dev_o = dev;
    }
done:
    UNLOCK_devinit(); // ------------------------------------------
    return rv;
}

/* ============================================================ */
/* Device access */

#define DEV_INIT_CHECK(dev) do { if (!dev->is_inited) return -ENXIO; } while(0)
#define PARTITION_CHECK(p) do { if (!p) return -EBADF; if (!p->dev) return -ENXIO; } while(0)

// Partition-to-Device and Device-to-Partition address xlation
#define BLOCK_P_TO_D(_part,_block) ((_block) + (_part)->first)
#define BLOCK_D_TO_P(_part,_block) ((_block) - (_part)->first)

#define PAGE_P_TO_D(_part,_page) ((_page) + (_part)->first * CYG_NAND_PAGES_PER_BLOCK((_part)->dev) )
#define PAGE_D_TO_P(_part,_page) ((_page) - (_part)->first * CYG_NAND_PAGES_PER_BLOCK((_part)->dev) )

/* Sanity check helpers: these take a partition and a DEVICE address */
static inline int valid_block_addr(cyg_nand_partition *part, cyg_nand_block_addr block)
{
    return ( (block < part->first) || (block > part->last) ) ? -ENOENT : 0;
}

static int valid_page_addr(cyg_nand_partition *part, cyg_nand_page_addr page)
{
    cyg_nand_block_addr block = CYG_NAND_PAGE2BLOCKADDR(part->dev,page);
    int rv = valid_block_addr(part, block);
    if (rv != 0)
        NAND_CHATTER(1,part->dev, "Invalid attempted access to page %u\n", (unsigned)page);
    return rv;
}

#define EG(what) do { rv = (what); if (rv != 0) goto err_exit; } while(0)

__externC
int cyg_nandp_read_page(cyg_nand_partition *prt, cyg_nand_page_addr ppage,
                void * dest, void * spare, size_t spare_size)
{
    int rv, locked=0;
    TIMETAG_INIT();
    PARTITION_CHECK(prt);
    cyg_nand_device *dev = prt->dev;
    DEV_INIT_CHECK(dev);
    cyg_nand_page_addr page = PAGE_P_TO_D(prt,ppage);

    if (spare_size > dev->spare_per_page) return -EFBIG;

    EG(valid_page_addr(prt, page));

#ifdef CYGSEM_IO_NAND_USE_BBT
    cyg_nand_block_addr blk = CYG_NAND_PAGE2BLOCKADDR(dev,page);
    if (cyg_nand_bbti_query(dev, blk) != CYG_NAND_BBT_OK) {
        NAND_CHATTER(1,dev,"Asked to read page %u in bad block %u\n", page, blk);
        EG(-EINVAL);
    }
#endif

    LOCK_DEV(dev);
    locked = 1;
    EG(nandi_read_whole_page_raw(dev, page, dest, spare, spare_size, 1));

err_exit:
    if (locked) UNLOCK_DEV(dev);
    TIMETAG();
    return rv;
}



__externC
int cyg_nandp_read_part_page(cyg_nand_partition *prt, cyg_nand_page_addr ppage,
                void * dest, size_t offset, size_t length, int check_ecc)

{
    int rv;
    PARTITION_CHECK(prt);
    cyg_nand_device *dev = prt->dev;
    DEV_INIT_CHECK(dev);
    cyg_nand_page_addr page = PAGE_P_TO_D(prt,ppage);
    CYG_BYTE *pagebuffer;
    int got_pagebuf = 0, locked = 0;

    if (offset + length > CYG_NAND_BYTES_PER_PAGE(dev)) return -EFBIG;

    EG(valid_page_addr(prt, page));

#ifdef CYGSEM_IO_NAND_USE_BBT
    cyg_nand_block_addr blk = CYG_NAND_PAGE2BLOCKADDR(dev,page);
    if (cyg_nand_bbti_query(dev, blk) != CYG_NAND_BBT_OK) {
        NAND_CHATTER(1,dev,"Asked to read page %u in bad block %u\n", page, blk);
        EG(-EINVAL);
    }
#endif

    if (length==0) return 0;

    LOCK_DEV(dev);
    locked = 1;

    if (dev->fns->read_part_page && !check_ecc) {
        EG(dev->fns->read_part_page(dev, dest, page, offset, length));
    } else {
        pagebuffer = nandi_grab_pagebuf();
        got_pagebuf = 1;
        EG(nandi_read_whole_page_raw(dev, page, pagebuffer, 0, 0, check_ecc));
        if (dest)
            memcpy(dest, &pagebuffer[offset], length);
    }

err_exit:
    if (got_pagebuf) nandi_release_pagebuf();
    if (locked) UNLOCK_DEV(dev);
    return rv;
}


/* Internal, mostly-unchecked interface to read a page. 
 * Takes a DEVICE address.
 * Caller must hold the devlock! */
int nandi_read_whole_page_raw(cyg_nand_device *dev, cyg_nand_page_addr page,
            CYG_BYTE * dest, CYG_BYTE * spare, size_t spare_size,
            int check_ecc)
{
    CYG_BYTE ecc_read[CYG_NAND_ECCPERPAGE(dev)],
             ecc_calc[CYG_NAND_ECCPERPAGE(dev)];
    CYG_BYTE *ecc_calc_p, *ecc_read_p;

    CYG_BYTE oob_buf[dev->spare_per_page];
    int rv=0,tries=0;
    size_t remain;
    const int do_hw_ecc = (dev->ecc->flags & NAND_ECC_FLAG_IS_HARDWARE) && check_ecc;

    // Stride for reading from device:
    const unsigned read_data_stride = do_hw_ecc ? dev->ecc->data_size : CYG_NAND_BYTES_PER_PAGE(dev);
    // Stride for calculating/checking/repairing ECC:
    const unsigned ecc_data_stride = dev->ecc->data_size;
    // Stride within the ECC data
    const unsigned ecc_stride = dev->ecc->ecc_size;

    if (dest)  CYG_CHECK_DATA_PTRC(dest);
    if (spare) CYG_CHECK_DATA_PTRC(spare);

    CYG_ASSERTC(CYG_NAND_BYTES_PER_PAGE(dev) % ecc_data_stride == 0);

    do {
        CYG_BYTE *data_dest = dest;
        CYG_BYTE *ecc_dest = ecc_calc;

        ++tries;
        remain = CYG_NAND_BYTES_PER_PAGE(dev);

        TIMETAG();
        EG(dev->fns->read_begin(dev, page));
        TIMETAG();

        if (dest) {
            while (remain) {
                CYG_ASSERTC(remain >= read_data_stride);

                TIMETAG();
                if (do_hw_ecc && dev->ecc->init_rd) dev->ecc->init_rd(dev);
                EG(dev->fns->read_stride(dev, data_dest, read_data_stride));
                TIMETAG();

                if (do_hw_ecc) {
                    dev->ecc->calc_rd(dev, 0, ecc_dest);
                    ecc_dest += ecc_stride;
                    TIMETAG();
                }

                data_dest += read_data_stride;
                remain -= read_data_stride;
            }

            memset(oob_buf, 0xff, CYG_NAND_SPARE_PER_PAGE(dev));
            TIMETAG();
            EG(dev->fns->read_finish(dev, oob_buf, dev->spare_per_page));
            TIMETAG();
            nand_oob_unpack(dev, spare, spare_size, ecc_read, oob_buf);

            if (check_ecc) {
                TIMETAG();
                if (!do_hw_ecc) {
                    // Calculate software ECC in one go to try and take
                    // advantage of the cache.
                    data_dest = dest;
                    remain = CYG_NAND_BYTES_PER_PAGE(dev);
                    ecc_calc_p = ecc_calc;

                    while (remain) {
                        if (dev->ecc->init_rd) dev->ecc->init_rd(dev);
                        dev->ecc->calc_rd(dev, data_dest, ecc_calc_p);

                        remain -= ecc_data_stride;
                        data_dest += ecc_data_stride;
                        ecc_calc_p += ecc_stride;
                    }
                }

                // Now repair ...
                rv = 0;
                int step_rv;
                remain = CYG_NAND_BYTES_PER_PAGE(dev);
                data_dest = dest;

                ecc_calc_p = ecc_calc;
                ecc_read_p = ecc_read;

                while (remain) {
                    CYG_ASSERTC(remain >= ecc_data_stride);

                    step_rv = dev->ecc->repair(dev,data_dest,ecc_data_stride,ecc_read_p,ecc_calc_p);
                    if (step_rv == -1) {
                        rv = -1;
                        break;
                    }
                    rv |= step_rv;

                    data_dest += ecc_data_stride;
                    ecc_read_p += ecc_stride;
                    ecc_calc_p += ecc_stride;
                    remain -= ecc_data_stride;
                }
                TIMETAG();
            }
        } else { // !dest: very simple case
            TIMETAG();
            EG(dev->fns->read_finish(dev, oob_buf, dev->spare_per_page));
            TIMETAG();
            rv = 0;
            nand_oob_unpack(dev, spare, spare_size, ecc_read, oob_buf);
        }

        if (rv==-1 && (tries < CYGNUM_NAND_MAX_READ_RETRIES) ) {
            NAND_CHATTER(4, dev, "ECC uncorrectable error on read, retrying\n");
        }
    } while (rv==-1 && (tries < CYGNUM_NAND_MAX_READ_RETRIES) );

    switch (rv) {
        case 0:
            NAND_CHATTER(8,dev,"Read page %u OK\n", page);
            break;
        case 1:
        case 2:
        case 3:
            NAND_CHATTER(2,dev, "Page %u ECC correction, type %d\n", page,rv);
            rv=0;
            break;
        case -1:
        default:
            NAND_ERROR(dev,"Page %u read gave ECC uncorrectable error\n", page);
            rv=-EIO;
            break;
    }
err_exit:
    return rv;
}

__externC
int cyg_nandp_write_page(cyg_nand_partition *prt, cyg_nand_page_addr ppage,
        const void * src, const void * spare, size_t spare_size)
{
    int rv, locked = 0;
    TIMETAG_INIT();
    PARTITION_CHECK(prt);
    cyg_nand_device *dev = prt->dev;
    DEV_INIT_CHECK(dev);
    cyg_nand_page_addr page = PAGE_P_TO_D(prt,ppage);

    EG(valid_page_addr(prt, page));

#ifdef CYGSEM_IO_NAND_USE_BBT
    cyg_nand_block_addr blk = CYG_NAND_PAGE2BLOCKADDR(dev,page);
    if (cyg_nand_bbti_query(dev, blk) != CYG_NAND_BBT_OK) {
        NAND_CHATTER(1,dev,"Asked to write page %u in bad block %u\n", page, blk);
        EG(-EINVAL);
    }
#endif
    LOCK_DEV(dev);
    locked = 1;
    EG(nandi_write_page_raw_explicit_ecc(dev, page, src, spare, spare_size, 0, 0));

err_exit:
    if (locked) UNLOCK_DEV(dev);
    TIMETAG();
    return rv;
}

/* Internal, mostly-unchecked interface to write a page. 
 * Takes a DEVICE address. */
__externC
int nandi_write_page_raw(cyg_nand_device *dev, cyg_nand_page_addr page,
        const CYG_BYTE * src, const CYG_BYTE * spare, size_t spare_size)
{
    return nandi_write_page_raw_explicit_ecc(dev, page, src, spare, spare_size, 0, 0);
}

__externC
int nandi_write_page_raw_explicit_ecc(cyg_nand_device *dev, cyg_nand_page_addr page,
        const CYG_BYTE * src, const CYG_BYTE * spare, size_t spare_size, const CYG_BYTE * xecc, size_t xecc_size)
{
#ifdef CYGSEM_IO_NAND_READONLY
    return -EROFS;
#else
    int rv;
    CYG_BYTE oob_packed[dev->spare_per_page];
    CYG_BYTE ecc[CYG_NAND_ECCPERPAGE(dev)];

    CYG_ASSERTC((xecc_size==0) || (xecc_size==CYG_NAND_ECCPERPAGE(dev)));

    const int do_hw_ecc = (dev->ecc->flags & NAND_ECC_FLAG_IS_HARDWARE);
    const unsigned write_data_stride = do_hw_ecc ? dev->ecc->data_size : CYG_NAND_BYTES_PER_PAGE(dev);
    const unsigned ecc_data_stride = dev->ecc->data_size;
    const unsigned ecc_stride = dev->ecc->ecc_size;
    size_t remain;
    CYG_BYTE *ecc_dest = ecc;

    memset(ecc, 0xff, CYG_NAND_ECCPERPAGE(dev));

    if (src)   CYG_CHECK_DATA_PTRC(src);
    if (spare) CYG_CHECK_DATA_PTRC(spare);
    CYG_ASSERTC(CYG_NAND_BYTES_PER_PAGE(dev) % ecc_data_stride == 0);

    TIMETAG();
    // If we're doing software ECC, do it all in one go now.
    if (src && !do_hw_ecc) {
        const CYG_BYTE *data_src = src;
        remain = CYG_NAND_BYTES_PER_PAGE(dev);

        while (remain) {
            CYG_ASSERTC(remain >= ecc_data_stride);
            if (dev->ecc->init_wr) dev->ecc->init_wr(dev);
            dev->ecc->calc_wr(dev, data_src, ecc_dest);

            remain -= ecc_data_stride;
            data_src += ecc_data_stride;
            ecc_dest += ecc_stride;
        }
    }
    TIMETAG();

    EG(dev->fns->write_begin(dev, page));
    TIMETAG();
    if (src) {
        remain = CYG_NAND_BYTES_PER_PAGE(dev);
        while (remain) {
            CYG_ASSERTC(remain >= write_data_stride);
            TIMETAG();
            if (do_hw_ecc && dev->ecc->init_wr) dev->ecc->init_wr(dev);
            EG(dev->fns->write_stride(dev, src, write_data_stride));
            TIMETAG();
            if (do_hw_ecc) {
                dev->ecc->calc_wr(dev, 0, ecc_dest);
                ecc_dest += ecc_stride;
            }
            src += write_data_stride;
            remain -= write_data_stride;
        }
    }
    TIMETAG();

    if (xecc_size) memcpy(&ecc[0], xecc, xecc_size);
    nand_oob_pack(dev, spare, spare_size, ecc, oob_packed);
    NAND_CHATTER(8,dev,"Write page %u\n", page);
    EG(dev->fns->write_finish(dev, oob_packed, dev->spare_per_page));
    TIMETAG();

    /* N.B. We don't read-back to verify; drivers may do so themselves if
     * they wish. Typically the spec sheet says that a read-back test
     * is unnecessary if the device reports a successful program, and 
     * if ECC is being used. */

err_exit:
    return rv;
#endif
}

__externC
int cyg_nandp_erase_block(cyg_nand_partition *prt, cyg_nand_block_addr pblk)
{
#ifdef CYGSEM_IO_NAND_READONLY
    return -EROFS;
#else
    PARTITION_CHECK(prt);
    cyg_nand_device *dev = prt->dev;
    int rv, state;
    DEV_INIT_CHECK(dev);
    cyg_nand_block_addr blk = BLOCK_P_TO_D(prt, pblk);

    TIMETAG();
    LOCK_DEV(dev);
    TIMETAG();

    EG(valid_block_addr(prt, blk));
#ifdef CYGSEM_IO_NAND_USE_BBT
	state = cyg_nand_bbti_query(dev, blk);
    if ( (state != CYG_NAND_BBT_OK)
#ifdef CYGSEM_IO_NAND_ALLOW_ERASE_WORNBAD
			 && (state != CYG_NAND_BBT_WORNBAD)
#endif
			) {
        NAND_CHATTER(1,dev,"Asked to erase bad block %u\n", blk);
        EG(-EINVAL);
    }
#else
    (void) state;
#endif
    NAND_CHATTER(8,dev,"Erasing block %u\n", blk);
    rv = dev->fns->erase_block(dev, blk);
    if (rv==-EIO) {
#ifdef CYGSEM_IO_NAND_USE_BBT
        cyg_nand_bbti_markbad(dev, blk); // deliberate ignore
#endif
        EG(rv);
    }
err_exit:
    TIMETAG();
    UNLOCK_DEV(dev);
    TIMETAG();
    return rv;
#endif
}

__externC
int cyg_nandp_bbt_query(cyg_nand_partition *prt, cyg_nand_block_addr pblk)
{
#ifdef CYGSEM_IO_NAND_USE_BBT
    int rv;
    PARTITION_CHECK(prt);
    cyg_nand_device *dev = prt->dev;
    DEV_INIT_CHECK(dev);
    cyg_nand_block_addr blk = BLOCK_P_TO_D(prt, pblk);
    LOCK_DEV(dev);
    EG(valid_block_addr(prt, blk));
    rv = cyg_nand_bbti_query(dev, blk);
err_exit:
    UNLOCK_DEV(dev);
    return rv;
#else
    return -ENOSYS;
#endif
}

__externC
int cyg_nandp_bbt_markbad(cyg_nand_partition *prt, cyg_nand_block_addr pblk)
{
#ifdef CYGSEM_IO_NAND_USE_BBT
    int rv;
    PARTITION_CHECK(prt);
    cyg_nand_device *dev = prt->dev;
    DEV_INIT_CHECK(dev);
    cyg_nand_block_addr blk = BLOCK_P_TO_D(prt, pblk);
    LOCK_DEV(dev);
    EG(valid_block_addr(prt, blk));
    EG(cyg_nand_bbti_markbad(dev, blk));
err_exit:
    UNLOCK_DEV(dev);
    return rv;
#else
    return -ENOSYS;
#endif
}

__externC
int cyg_nandp_bbt_markbad_pageaddr(cyg_nand_partition *prt, cyg_nand_page_addr ppg)
{
#ifdef CYGSEM_IO_NAND_USE_BBT
    int rv;
    PARTITION_CHECK(prt);
    cyg_nand_device *dev = prt->dev;
    DEV_INIT_CHECK(dev);
    cyg_nand_block_addr pblk = CYG_NAND_PAGE2BLOCKADDR(dev, ppg);
    cyg_nand_block_addr blk = BLOCK_P_TO_D(prt, pblk);
    EG(valid_block_addr(prt, blk));
    LOCK_DEV(dev);
    EG(cyg_nand_bbti_markbad(dev, blk));
err_exit:
    UNLOCK_DEV(dev);
    return rv;
#else
    return -ENOSYS;
#endif
}

/* Computes the ECC for a whole device page.
 * This is intended for use with software ECC only! (The init_wr
 * and calc_wr functions will be used.)
 *
 * 'page' points to the data; a whole page will necessarily be read.
 * The computed ECC will be stored in 'ecc_o'; CYG_NAND_ECCPERPAGE(dev)
 * bytes will be written. */
void nand_ecci_calc_page(cyg_nand_device *dev, const CYG_BYTE *page, CYG_BYTE *ecc_o)
{
    int i;
    const int nblocks = (1<<dev->page_bits) / dev->ecc->data_size;

    CYG_CHECK_DATA_PTRC(page);
    CYG_CHECK_DATA_PTRC(ecc_o);

    for (i=0; i<nblocks; i++) {
        if (dev->ecc->init_wr)
            dev->ecc->init_wr(dev);
        dev->ecc->calc_wr(dev,page,ecc_o);
        page += dev->ecc->data_size;
        ecc_o += dev->ecc->ecc_size;
    }
}

/* Checks and (if necessary) repairs the ECC for (up to) a whole device page.
 * 'page' points to the data; an error at position after @nbytes@ will not
 * be corrected.
 * Broadly the same semantics as for cyg_nand_ecc_t.repair; 
 * both ECCs are of size CYG_NAND_ECCPERPAGE(dev), and ecc_read may
 * be corrected as well as the data.
 * Returns:
 *      0 for no errors
 *      1 if there was at least one corrected data error
 *      2 if there was at least one corrected ECC error
 *      3 if there was at least one corrected error in both data and ECC
 *     -1 if there was an uncorrectable error (>1 bit in a single ECC block)
 */
int nand_ecci_repair_page(cyg_nand_device *dev, CYG_BYTE *page, size_t remain,
        CYG_BYTE *ecc_read, const CYG_BYTE *ecc_calc)
{
    int i, page_rv=0;
    const int nblocks = (1<<dev->page_bits) / dev->ecc->data_size;
    CYG_CHECK_DATA_PTRC(page);
    CYG_CHECK_DATA_PTRC(ecc_read);
    CYG_CHECK_DATA_PTRC(ecc_calc);

    for (i=0; i<nblocks; i++) {
        int stride = dev->ecc->data_size;
        if (stride > remain) stride = remain;
        int chunk_rv = dev->ecc->repair(dev,page,stride,ecc_read,ecc_calc);
        if (chunk_rv < 0) return chunk_rv;
        page_rv |= chunk_rv;
        page += dev->ecc->data_size;
        remain -= dev->ecc->data_size;
        ecc_read += dev->ecc->ecc_size;
        ecc_calc += dev->ecc->ecc_size;
    }
    return page_rv;
}

