diff packages/io/nand/current/src/nand.c @ 2930:6efd22c2e7b1

Import anoncvs-based NAND and device drivers to 20090826
author Ross Younger <wry@ecoscentric.com>
date Mon, 02 Nov 2009 16:30:33 +0000
parents
children 32bcd454bda4
line wrap: on
line diff
new file mode 100644
--- /dev/null
+++ b/packages/io/nand/current/src/nand.c
@@ -0,0 +1,485 @@
+//=============================================================================
+//
+//      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>
+#include <cyg/nand/util.h>
+#include "nand_bbt.h"
+#include CYGBLD_ISO_ERRNO_CODES_HEADER
+#include <string.h>
+
+extern cyg_drv_mutex_t nand_bbt_pagebuf_lock; // Lives in nand_bbt.c
+
+/* ============================================================ */
+
+/* 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 */
+
+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_drv_mutex_init(&nand_bbt_pagebuf_lock);
+    nand_default_pf = pf;
+}
+
+__externC
+cyg_nand_partition* cyg_nand_get_partition(cyg_nand_device *dev, unsigned partno)
+{
+    if ((partno < 0) || (partno >= CYGNUM_NAND_MAX_PARTITIONS))
+        return NULL;
+    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;
+            CYG_CHECK_DATA_PTRC(dev->fns);
+            CYG_CHECK_FUNC_PTRC(dev->fns->devinit);
+            CYG_CHECK_FUNC_PTRC(dev->fns->read_page);
+            CYG_CHECK_FUNC_PTRC(dev->fns->write_page);
+            CYG_CHECK_FUNC_PTRC(dev->fns->erase_block);
+            CYG_CHECK_FUNC_PTRC(dev->fns->is_factory_bad);
+
+            dev->version = 1;
+            dev->pf = nand_default_pf;
+            for (i=0; i<CYGNUM_NAND_MAX_PARTITIONS; i++)
+                dev->partition[i].dev = 0;
+            dev->bbt.data = 0; // Paranoia, ensure devinit sets up
+            rv = dev->fns->devinit(dev);
+            if (rv) {
+                NAND_ERROR(dev,"Could not initialise NAND device \"%s\": code %d\n", devname, rv);
+                goto done;
+            }
+            CYG_CHECK_DATA_PTRC(dev->bbt.data);
+            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 ||
+                    !dev->bbt.data ||
+                    !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;
+            }
+
+            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;
+            }
+
+            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;
+            }
+
+            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;
+            }
+
+            cyg_drv_mutex_init(&dev->devlock);
+            dev->is_inited = 1;
+
+            int 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 devinit complete, %u partitions configured\n", devname, live_partitions);
+            else
+                NAND_CHATTER(1,dev, "%s devinit complete, NO partitions configured!\n", devname); // hope they know what they're doing.
+        }
+        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->dev) return -ENXIO; } while(0)
+
+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 %d\n", page);
+    return rv;
+}
+
+#define EG(what) do { rv = (what); if (rv != 0) goto err_exit; } while(0)
+
+__externC
+int cyg_nand_read_page(cyg_nand_partition *prt, cyg_nand_page_addr page,
+                void * dest, size_t size, void * spare, size_t spare_size)
+{
+    int tries=0;
+    int rv;
+    PARTITION_CHECK(prt);
+    cyg_nand_device *dev = prt->dev;
+    DEV_INIT_CHECK(dev);
+
+    if (dest)  CYG_CHECK_DATA_PTRC(dest);
+    if (spare) CYG_CHECK_DATA_PTRC(spare);
+
+    if (size > (1<<dev->page_bits)) return -EFBIG;
+    if (spare_size > dev->spare_per_page) return -EFBIG;
+
+    LOCK_DEV(dev);
+
+    CYG_BYTE ecc_read[CYG_NAND_ECCPERPAGE(dev)],
+             ecc_calc[CYG_NAND_ECCPERPAGE(dev)];
+    CYG_BYTE oob_buf[dev->spare_per_page];
+
+    EG(valid_page_addr(prt, page));
+    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);
+    }
+
+    do {
+        ++tries;
+
+        EG(dev->fns->read_page(dev, page, dest, size, oob_buf, dev->spare_per_page));
+
+        nand_oob_unpack(dev, spare, spare_size, ecc_read, oob_buf);
+
+        if (!dest) goto err_exit; // No data? Can't ECC it!
+        if (size != 1<<dev->page_bits) goto err_exit;
+        // FIXME: Make part-page ECC work.
+
+        nand_ecci_calc_page(dev, dest, ecc_calc);
+        rv = nand_ecci_repair_page(dev, dest, ecc_read, ecc_calc);
+        if (rv==-1 && (tries < CYGNUM_NAND_MAX_READ_RETRIES) ) {
+            NAND_CHATTER(4, dev, "NAND: 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:
+            NAND_ERROR(dev,"NAND: Page %u read gave ECC uncorrectable error\n", page);
+            rv=-EIO;
+            break;
+        case 1:
+        case 2:
+        case 3:
+            NAND_CHATTER(2,dev, "Page %u ECC correction, type %d\n", page,rv);
+            rv=0;
+            break;
+    }
+err_exit:
+    UNLOCK_DEV(dev);
+    return rv;
+}
+
+__externC
+int cyg_nand_write_page(cyg_nand_partition *prt, cyg_nand_page_addr page,
+        const void * src, size_t size, const void * spare, size_t spare_size)
+{
+#ifdef CYGSEM_IO_NAND_READONLY
+    return -EROFS;
+#else
+    int rv;
+    PARTITION_CHECK(prt);
+    cyg_nand_device *dev = prt->dev;
+    DEV_INIT_CHECK(dev);
+
+    if (src)   CYG_CHECK_DATA_PTRC(src);
+    if (spare) CYG_CHECK_DATA_PTRC(spare);
+
+    LOCK_DEV(dev);
+
+    CYG_BYTE ecc[CYG_NAND_ECCPERPAGE(dev)];
+    CYG_BYTE oob_packed[dev->spare_per_page];
+
+    EG(valid_page_addr(prt, page));
+    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);
+    }
+
+    if (src && (size == 1<<dev->page_bits)) {
+        nand_ecci_calc_page(dev, src, ecc);
+        // FIXME: Make ECC work on part-pages.
+    } else {
+        // No data, can't compute an ECC, hope they're not overwriting...
+        memset(ecc, 0xff, CYG_NAND_ECCPERPAGE(dev));
+    }
+    nand_oob_pack(dev, spare, spare_size, ecc, oob_packed);
+
+    NAND_CHATTER(8,dev,"Write page %u\n", page);
+    EG(dev->fns->write_page(dev, page, src, size, oob_packed, dev->spare_per_page));
+    /* 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:
+    UNLOCK_DEV(dev);
+    return rv;
+#endif
+}
+
+__externC
+int cyg_nand_erase_block(cyg_nand_partition *prt, cyg_nand_block_addr blk)
+{
+#ifdef CYGSEM_IO_NAND_READONLY
+    return -EROFS;
+#else
+    PARTITION_CHECK(prt);
+    cyg_nand_device *dev = prt->dev;
+    int rv;
+    DEV_INIT_CHECK(dev);
+
+    LOCK_DEV(dev);
+
+    EG(valid_block_addr(prt, blk));
+    if (cyg_nand_bbti_query(dev, blk) != CYG_NAND_BBT_OK) {
+        NAND_CHATTER(1,dev,"Asked to erase bad block %u\n", blk);
+        EG(-EINVAL);
+    }
+    NAND_CHATTER(8,dev,"Erasing block %u\n", blk);
+    rv = dev->fns->erase_block(dev, blk);
+    if (rv==-EIO) {
+        cyg_nand_bbti_markbad(dev, blk); // deliberate ignore
+        EG(rv);
+    }
+err_exit:
+    UNLOCK_DEV(dev);
+    return rv;
+#endif
+}
+
+__externC
+int cyg_nand_bbt_query(cyg_nand_partition *prt, cyg_nand_block_addr blk)
+{
+    int rv;
+    PARTITION_CHECK(prt);
+    cyg_nand_device *dev = prt->dev;
+    DEV_INIT_CHECK(dev);
+    LOCK_DEV(dev);
+    EG(valid_block_addr(prt, blk));
+    rv = cyg_nand_bbti_query(dev, blk);
+err_exit:
+    UNLOCK_DEV(dev);
+    return rv;
+}
+
+__externC
+int cyg_nand_bbt_markbad(cyg_nand_partition *prt, cyg_nand_block_addr blk)
+{
+    int rv;
+    PARTITION_CHECK(prt);
+    cyg_nand_device *dev = prt->dev;
+    DEV_INIT_CHECK(dev);
+    LOCK_DEV(dev);
+    EG(valid_block_addr(prt, blk));
+    EG(cyg_nand_bbti_markbad(dev, blk));
+err_exit:
+    UNLOCK_DEV(dev);
+    return rv;
+}
+
+__externC
+int cyg_nand_bbt_markbad_pageaddr(cyg_nand_partition *prt, cyg_nand_page_addr pg)
+{
+    int rv;
+    PARTITION_CHECK(prt);
+    cyg_nand_device *dev = prt->dev;
+    DEV_INIT_CHECK(dev);
+    LOCK_DEV(dev);
+    cyg_nand_block_addr blk = CYG_NAND_PAGE2BLOCKADDR(dev, pg);
+    EG(valid_block_addr(prt, blk));
+    EG(cyg_nand_bbti_markbad(dev, blk));
+err_exit:
+    UNLOCK_DEV(dev);
+    return rv;
+}
+
+/* Computes the ECC for a whole device page.
+ * '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++) {
+        dev->ecc->calc(page,ecc_o);
+        page += dev->ecc->data_size;
+        ecc_o += dev->ecc->ecc_size;
+    }
+}
+
+/* Checks and (if necessary) repairs the ECC for a whole device page.
+ * 'page' points to the data; a whole page will necessarily be read.
+ * 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, 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 chunk_rv = dev->ecc->repair(page,ecc_read,ecc_calc);
+        if (chunk_rv < 0) return chunk_rv;
+        page_rv |= chunk_rv;
+        page += dev->ecc->data_size;
+        ecc_read += dev->ecc->ecc_size;
+        ecc_calc += dev->ecc->ecc_size;
+    }
+    return page_rv;
+}
+