summaryrefslogblamecommitdiff
path: root/nuttx/drivers/mtd/mtd_nand.c
blob: 3c333dd47727c339e15dd1224f30c2b5d201c9d6 (plain) (tree)
1
2
3
4
5
6
7
8
9
                                                                             
                         



                                                          

                                                                           
                                                          









































                                                                              
                      
                   
                  

                  


                           

                           
                               

                                  
                               



                                                                              

                                                             





                                 







                                                                              




                                                       
                        





                                                                         
                                  
      
 


                                                    

                                                            
                                                                       
                                                        
                                                                        
                                                           
 




                                                                  
                                                  
                                                                   
                                                        


                                                         








                                                                              
































                                                                              



















                                                                              


                                         




                                 
                                     






                                              
                                              

              
                                                                





                                                        
                                                            




                                               
                                              

              
                                                                



                                                        
                     
     
                                                            












                                                                             




                                                                            



                                                    
                


                                                                              

                                                                                                    

                                                    


                                 
              



                                                             





                                  

                        
 
                                          


                                 
                                                                   


                                                              



                                                             
                                           





                                         






                                                                  









                                                                     










                                                             

     






                                                             




                                                                             





































                                                                              















































































                                                                              
                                                       






                                                                              
                                                              
 

                                                              







                                                            
      
 

                                                          
 
                                 
                                          


                                                    
                                                             






                                                                    
      
     
                                                               
     












                                                                             
                                                       






                                                                              
                                                                  








                                                
      
 



                                                           
                                          


                                                    
                                                              
     
 





                                                                    
     
                                                                
     


                                                                             









                                                                              


                                                             
 
                                                                           
 
                                                           
 


















                                                           









                                                                              
                                                                 
                                                             
 









                                                             
 
                                                                     





                                 
 

                                                                    
     
 






                                                                   
                                    








                                                          










                                                                  

                                        
                                                     














                                                                     
                  



                                                              
                         







                    









                                                                              
                                                                  
                                                                
 









                                                             
 



                                                                       
 




                                                                    
     
 






                                                                   
                                    








                                                           










                                                                   

                                         
                                                      

                  
                                                                      











                                                                
                  



                                                              
                         







                    







                                                                              
                                                             

                                 

                                                                  



                                 







                                                                                
                                                                     
                


                                                                        

                 


                                                                    








                                       
                                                                  












                                                                             









                                                                              
                                                    


                                         

                                                                    

                  

                                                                     


                                                                              
                                                                 
 
                              
                             


                                               

                                                            


                                                   
                                                                  

                                                           

                                                               
                  



                                                          
                                                                     

              














                                                               


      
                                                   


                                                 


                                              



                                                                                      
 


                                                       
 
                                              
                                                
 

                                                       
 
                                              
                                                



                            
                                                   


                   
                                                   


                    
                                                    


                    
                                                    







                                                                        

                                             
                                                                        
            






                                                                        




                                 
 

                                 

                                      
                           


                                                                            
                    
 
/****************************************************************************
 * drivers/mtd/mtd_nand.c
 *
 *   Copyright (C) 2013 Gregory Nutt. All rights reserved.
 *   Author: Gregory Nutt <gnutt@nuttx.org>
 *
 * This logic was based largely on Atmel sample code with modifications for
 * better integration with NuttX.  The Atmel sample code has a BSD
 * compatible license that requires this copyright notice:
 *
 *   Copyright (c) 2011, 2012, Atmel Corporation
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. 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.
 * 3. Neither the names NuttX nor Atmel 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 THE
 * COPYRIGHT OWNER OR 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.
 *
 ****************************************************************************/

/****************************************************************************
 * Included Files
 ****************************************************************************/

#include <nuttx/config.h>
#include <nuttx/mtd/nand_config.h>

#include <sys/types.h>
#include <stdint.h>
#include <string.h>
#include <semaphore.h>
#include <assert.h>
#include <errno.h>
#include <debug.h>

#include <nuttx/kmalloc.h>
#include <nuttx/fs/ioctl.h>
#include <nuttx/mtd/mtd.h>
#include <nuttx/mtd/nand.h>
#include <nuttx/mtd/onfi.h>
#include <nuttx/mtd/nand_raw.h>
#include <nuttx/mtd/nand_scheme.h>
#include <nuttx/mtd/nand_model.h>
#include <nuttx/mtd/nand_ecc.h>

/****************************************************************************
 * Pre-processor Definitions
 ****************************************************************************/
/* Success Values returned by the nand_checkblock function */

#define GOODBLOCK            254
#define BADBLOCK             255

/* Bad block marker */

#define NAND_BLOCKSTATUS_BAD 0xba

/****************************************************************************
 * Private Types
 ****************************************************************************/

/****************************************************************************
 * Private Function Prototypes
 ****************************************************************************/
/* NAND locking */

static int      nand_lock(FAR struct nand_dev_s *nand);
#define         nand_unlock(n) sem_post(&(n)->exclsem)

/* Bad block checking */

#ifdef CONFIG_MTD_NAND_BLOCKCHECK
static int     nand_checkblock(FAR struct nand_dev_s *nand, off_t block);
static int     nand_devscan(FAR struct nand_dev_s *nand);
#else
#  define      nand_checkblock(n,b) (GOODBLOCK)
#  define      nand_devscan(n) (0)
#endif

/* Misc. NAND helpers */

static uint32_t nand_chipid(struct nand_raw_s *raw);
static int      nand_eraseblock(FAR struct nand_dev_s *nand,
                  off_t block, bool scrub);
static int      nand_readpage(FAR struct nand_dev_s *nand, off_t block,
                  unsigned int page, FAR uint8_t *data);
static int      nand_writepage(FAR struct nand_dev_s *nand, off_t block,
                  unsigned int page, FAR const void *data);

/* MTD driver methods */

static int     nand_erase(struct mtd_dev_s *dev, off_t startblock,
                 size_t nblocks);
static ssize_t nand_bread(struct mtd_dev_s *dev, off_t startblock,
                 size_t nblocks, uint8_t *buffer);
static ssize_t nand_bwrite(struct mtd_dev_s *dev, off_t startblock,
                 size_t nblocks, const uint8_t *buffer);
static int     nand_ioctl(struct mtd_dev_s *dev, int cmd,
                 unsigned long arg);

/****************************************************************************
 * Private Data
 ****************************************************************************/

/****************************************************************************
 * Private Functions
 ****************************************************************************/

/****************************************************************************
 * Name: nand_lock
 *
 * Description:
 *   Get exclusive access to the nand.
 *
 * Input Parameters:
 *   nand  - Pointer to a struct nand_dev_s instance.
 *   block - Number of block to check.
 *
 * Returned Value:
 *   OK on success; a negater errno value on failure.
 *
 ****************************************************************************/

static int nand_lock(FAR struct nand_dev_s *nand)
{
  int errcode;
  int ret;

  ret = sem_wait(&nand->exclsem);
  if (ret < 0)
    {
      errcode = errno;
      DEBUGASSERT(errcode != OK);

      fdbg("sem_wait failed: %d\n", errcode);
      return -errcode;
    }

  return OK;
}

/****************************************************************************
 * Name: nand_checkblock
 *
 * Description:
 *   Read and check for a bad block.
 *
 * Input Parameters:
 *   nand  - Pointer to a struct nand_dev_s instance.
 *   block - Number of block to check.
 *
 * Returned Value:
 *   Returns BADBLOCK if the given block of a nandflash device is bad;
 *   returns GOODBLOCK if the block is good; or returns negated errno
 *   value on any failure.
 *
 ****************************************************************************/

#ifdef CONFIG_MTD_NAND_BLOCKCHECK
static int nand_checkblock(FAR struct nand_dev_s *nand, off_t block)
{
  uint8_t spare[CONFIG_MTD_NAND_MAXPAGESPARESIZE];
  FAR const struct nand_scheme_s *scheme;
  FAR struct nand_raw_s *raw;
  FAR struct nand_model_s *model;
  uint8_t marker;
  int ret;

  DEBUGASSERT(nand && nand->raw);

  /* Retrieve the model and scheme */

  raw    = nand->raw;
  model  = &raw->model;
  scheme = nandmodel_getscheme(model);

  /* Read spare area of first page of block */

  ret = NAND_RAWREAD(raw, block, 0, 0, spare);
  if (ret < 0)
    {
      fdbg("ERROR: Failed to read page 0 of block %d\n", block);
      return ret;
    }

  nandscheme_readbadblockmarker(scheme, spare, &marker);
  if (marker != 0xff)
    {
      fvdbg("Page 0 block %d marker=%02x\n", block, marker);
      return BADBLOCK;
    }

  /* Read spare area of second page of block */

  ret = NAND_RAWREAD(raw, block, 1, 0, spare);
  if (ret < 0)
    {
      fdbg("ERROR: Failed to read page 1 of block %d\n", block);
      return ret;
    }

  nandscheme_readbadblockmarker(scheme, spare, &marker);
  if (marker != 0xff)
    {
      fvdbg("Page 1 block %d marker=%02x\n", block, marker);
      return BADBLOCK;
    }

  return GOODBLOCK;
}
#endif /* CONFIG_MTD_NAND_BLOCKCHECK */

/****************************************************************************
 * Name: nand_devscan
 *
 * Description:
 *   Scans the device to retrieve or create block status information.
 *
 *   Currently, this functin does nothing but scan the NAND and eat up time.
 *   This is a goot thing to do if you are debugging NAND, but otherwise,
 *   just a waste of time.  This logic could, however, be integrated into
 *   some bad block checking logic at sometime in the future.
 *
 * Input Parameters:
 *   nand - Pointer to a struct nand_dev_s instance.
 *
 * Returned Value:
 *   OK (always)
 *
 ****************************************************************************/

//#ifdef CONFIG_MTD_NAND_BLOCKCHECK
#if defined(CONFIG_MTD_NAND_BLOCKCHECK) && defined(CONFIG_DEBUG_VERBOSE) && defined(CONFIG_DEBUG_FS)
static int nand_devscan(FAR struct nand_dev_s *nand)
{
  FAR struct nand_raw_s *raw;
  FAR struct nand_model_s *model;
  off_t nblocks;
  off_t block;
#if defined(CONFIG_DEBUG_VERBOSE) && defined(CONFIG_DEBUG_FS)
  off_t good;
  unsigned int ngood;
#endif
  int ret;

  DEBUGASSERT(nand && nand->raw);

  /* Retrieve model information */

  raw     = nand->raw;
  model   = &raw->model;

  nblocks = nandmodel_getdevblocks(model);

  /* Initialize block statuses */

  fvdbg("Retrieving bad block information. nblocks=%d\n", nblocks);

  /* Retrieve block status from their first page spare area */

#if defined(CONFIG_DEBUG_VERBOSE) && defined(CONFIG_DEBUG_FS)
  ngood = 0;
#endif

  for (block = 0; block < nblocks; block++)
    {
      /* Read spare of first page */

      ret = nand_checkblock(nand, block);
      if (ret != GOODBLOCK)
        {
#if defined(CONFIG_DEBUG_VERBOSE) && defined(CONFIG_DEBUG_FS)
          if (ngood > 0)
            {
              fvdbg("Good blocks: %u - %u\n", good, good + ngood);
              ngood = 0;
            }
#endif
          if (ret == BADBLOCK)
            {
              fvdbg("Block %u is bad\n", (unsigned int)block);
            }
          else
            {
              fdbg("ERROR: Cannot retrieve info from block %u: %d\n",
                   (unsigned int)block, ret);
            }
        }
#if defined(CONFIG_DEBUG_VERBOSE) && defined(CONFIG_DEBUG_FS)
      else
        {
           if (ngood == 0)
             {
               good = block;
             }

           ngood++;
        }
#endif
    }

#if defined(CONFIG_DEBUG_VERBOSE) && defined(CONFIG_DEBUG_FS)
  if (ngood > 0)
    {
      fvdbg("Good blocks: %u - %u\n", good, good + ngood);
    }
#endif

  return OK;
}
#endif /* CONFIG_MTD_NAND_BLOCKCHECK */

/****************************************************************************
 * Name: nand_chipid
 *
 * Description:
 *   Reads and returns the identifiers of a NAND FLASH chip
 *
 * Input Parameters:
 *   raw - Pointer to a struct nand_raw_s instance.
 *
 * Returned Value:
 *   id1|(id2<<8)|(id3<<16)|(id4<<24)
 *
 ****************************************************************************/

static uint32_t nand_chipid(struct nand_raw_s *raw)
{
  uint8_t id[5];

  DEBUGASSERT(raw);

  WRITE_COMMAND8(raw, COMMAND_READID);
  WRITE_ADDRESS8(raw, 0);

  id[0] = READ_DATA8(raw);
  id[1] = READ_DATA8(raw);
  id[2] = READ_DATA8(raw);
  id[3] = READ_DATA8(raw);
  id[4] = READ_DATA8(raw);

  fvdbg("Chip ID: %02x %02x %02x %02x %02x\n",
        id[0], id[1], id[2], id[3], id[4]);

  return  (uint32_t)id[0]        |
         ((uint32_t)id[1] << 8)  |
         ((uint32_t)id[2] << 16) |
         ((uint32_t)id[3] << 24);
}

/****************************************************************************
 * Name: nand_eraseblock
 *
 * Description:
 *   Erase one block.
 *
 * Input Parameters:
 *   nand   Pointer to a struct nand_dev_s instance
 *   block  Number of block to erase
 *   scrub  True: Erase bad blocks
 *
 * Returned Value
 *   OK on success; a negated errno value on failure.
 *
 ****************************************************************************/

static int nand_eraseblock(FAR struct nand_dev_s *nand, off_t block,
                           bool scrub)
{
  uint8_t spare[CONFIG_MTD_NAND_MAXPAGESPARESIZE];
  FAR const struct nand_scheme_s *scheme;
  FAR struct nand_model_s *model;
  FAR struct nand_raw_s *raw;
  int ret;

  /* fvdbg("Block %d\n", block); */
  DEBUGASSERT(nand && nand->raw);

#ifdef CONFIG_MTD_NAND_BLOCKCHECK
  if (scrub)
    {
      /* Check block status */

      if (nand_checkblock(nand, block) != GOODBLOCK)
        {
          fvdbg("Block is BAD\n");
          return -EAGAIN;
        }
    }
#endif

  /* Erase block */

  ret = NAND_ERASEBLOCK(nand->raw, block);
  if (ret < 0)
    {
      int tmp;

      fdbg("ERROR: Cannot erase block %ld\n", (long)block);

      /* Retrieve the model and scheme */

      raw    = nand->raw;
      model  = &raw->model;
      scheme = nandmodel_getscheme(model);

      /* Try to mark the block as BAD */

      memset(spare, 0xff, CONFIG_MTD_NAND_MAXPAGESPARESIZE);
      nandscheme_writebadblockmarker(scheme, spare, NAND_BLOCKSTATUS_BAD);
      tmp = NAND_WRITEPAGE(nand->raw, block, 0, 0, spare);
      if (tmp < 0)
        {
          fdbg("ERROR: Failed bo marke block %ld as BAD\n", (long)block);
        }
    }

  return ret;
}

/****************************************************************************
 * Name: nandecc_readpage
 *
 * Description:
 *   Reads the data area (only) of a page of a NAND FLASH into the
 *   provided buffer.
 *
 * Input parameters:
 *   nand  - Upper-half, NAND FLASH interface
 *   block - Number of the block where the page to read resides.
 *   page  - Number of the page to read inside the given block.
 *   data  - Buffer where the data area will be stored.
 *
 * Returned value.
 *   OK is returned in success; a negated errno value is returned on failure.
 *
 ****************************************************************************/

static int nand_readpage(FAR struct nand_dev_s *nand, off_t block,
                         unsigned int page, FAR uint8_t *data)
{
  fvdbg("block=%d page=%d data=%p\n", (int)block, page, data);

#ifdef CONFIG_MTD_NAND_BLOCKCHECK
  /* Check that the block is not BAD if data is requested */

  if (nand_checkblock(nand, block) != GOODBLOCK)
    {
      fdbg("ERROR: Block is BAD\n");
      return -EAGAIN;
   }
#endif

#ifdef CONFIG_MTD_NAND_SWECC
  /* nandecc_readpage will handle the software ECC case */

  DEBUGASSERT(nand && nand->raw);
  if (nand->raw->ecctype == NANDECC_SWECC)
    {
      /* Read data with software ECC verification */

      return nandecc_readpage(nand, block, page, data, NULL);
    }

  /* The lower half will handle the No ECC and all hardware assisted
   * ECC calculations.
   */

  else
#endif
    {
      return NAND_READPAGE(nand->raw, block, page, data, NULL);
    }
}

/****************************************************************************
 * Name: nand_writepage
 *
 * Description:
 *   Writes the data area (only) of a page into NAND FLASH from the
 *   provided buffer.
 *
 * Input parameters:
 *   nand  - Upper-half, NAND FLASH interface
 *   block - Number of the block where the page to read resides.
 *   page  - Number of the page to read inside the given block.
 *   data  - Buffer where the data area will be stored.
 *
 * Returned value.
 *   OK is returned in success; a negated errno value is returned on failure.
 *
 ****************************************************************************/

static int nand_writepage(FAR struct nand_dev_s *nand, off_t block,
                          unsigned int page, FAR const void *data)
{
#ifdef CONFIG_MTD_NAND_BLOCKCHECK
  /* Check that the block is good */

  if (nand_checkblock(nand, block) != GOODBLOCK)
    {
      fdbg("ERROR: Block is BAD\n");
      return -EAGAIN;
    }
#endif

#ifdef CONFIG_MTD_NAND_SWECC
  /* nandecc_writepage will handle the software ECC case */

  DEBUGASSERT(nand && nand->raw);
  if (nand->raw->ecctype == NANDECC_SWECC)
    {
      /* Write data with software ECC calculation */

      return nandecc_writepage(nand, block, page, data, NULL);
    }

  /* The lower half will handle the No ECC and all hardware assisted
   * ECC calculations.
   */

 else
#endif
    {
      return NAND_WRITEPAGE(nand->raw, block, page, data, NULL);
    }
}

/****************************************************************************
 * Name: nand_erase
 *
 * Description:
 *   Erase several blocks, each of the size previously reported.
 *
 ****************************************************************************/

static int nand_erase(struct mtd_dev_s *dev, off_t startblock,
                      size_t nblocks)
{
  FAR struct nand_dev_s *nand = (FAR struct nand_dev_s *)dev;
  size_t blocksleft = nblocks;
  int ret;

  fvdbg("startblock: %08lx nblocks: %d\n", (long)startblock, (int)nblocks);

  /* Lock access to the NAND until we complete the erase */

  nand_lock(nand);
  while (blocksleft-- > 0)
    {
      /* Erase each sector */

      ret = nand_eraseblock(nand, startblock, false);
      if (ret < 0)
        {
          fdbg("nand_eraseblock failed on block %ld: %d\n",
               (long)startblock, ret);
          nand_unlock(nand);
          return ret;
        }

      startblock++;
    }

  nand_unlock(nand);
  return (int)nblocks;
}

/****************************************************************************
 * Name: nand_bread
 *
 * Description:
 *   Read the specified number of blocks into the user provided buffer.
 *
 ****************************************************************************/

static ssize_t nand_bread(struct mtd_dev_s *dev, off_t startpage,
                          size_t npages, FAR uint8_t *buffer)
{
  FAR struct nand_dev_s *nand = (FAR struct nand_dev_s *)dev;
  FAR struct nand_raw_s *raw;
  FAR struct nand_model_s *model;
  unsigned int pagesperblock;
  unsigned int page;
  uint16_t pagesize;
  size_t remaining;
  off_t maxblock;
  off_t block;
  int ret;

  fvdbg("startpage: %ld npages: %d\n", (long)startpage, (int)npages);
  DEBUGASSERT(nand && nand->raw);

  /* Retrieve the model */

  raw    = nand->raw;
  model  = &raw->model;

  /* Get the number of pages in one block, the size of one page, and
   * the number of blocks on the device.
   */

  pagesperblock = nandmodel_pagesperblock(model);
  pagesize      = nandmodel_getpagesize(model);
  maxblock      = nandmodel_getdevblocks(model);

  /* Get the block and page offset associated with the startpage */

  block = startpage / pagesperblock;
  page  = startpage % pagesperblock;

  /* Lock access to the NAND until we complete the read */

  nand_lock(nand);

  /* Then read every page from NAND */

  for (remaining = npages; remaining > 0; remaining--)
    {
      /* Check for attempt to read beyond the end of NAND */

      if (block > maxblock)
        {
          fdbg("ERROR: Read beyond the end of FLASH, block=%ld\n",
               (long)block);

          ret = -ESPIPE;
          goto errout_with_lock;
        }

      /* Read the next page from NAND */

      ret = nand_readpage(nand, block, page, buffer);
      if (ret < 0)
        {
          fdbg("ERROR: nand_readpage failed block=%ld page=%d: %d\n",
               (long)block, page, ret);
          goto errout_with_lock;
        }

      /* Increment the page number.  If we exceed the number of
       * pages per block, then reset the page number and bump up
       * the block number.
       */

      if (++page >= pagesperblock)
        {
          page = 0;
          block++;
        }

      /* Increment the buffer point by the size of one page */

      buffer += pagesize;
    }

  nand_unlock(nand);
  return npages;

errout_with_lock:
  nand_unlock(nand);
  return ret;
}

/****************************************************************************
 * Name: nand_bwrite
 *
 * Description:
 *   Write the specified number of blocks from the user provided buffer.
 *
 ****************************************************************************/

static ssize_t nand_bwrite(struct mtd_dev_s *dev, off_t startpage,
                           size_t npages, const uint8_t *buffer)
{
  FAR struct nand_dev_s *nand = (FAR struct nand_dev_s *)dev;
  FAR struct nand_raw_s *raw;
  FAR struct nand_model_s *model;
  unsigned int pagesperblock;
  unsigned int page;
  uint16_t pagesize;
  size_t remaining;
  off_t maxblock;
  off_t block;
  int ret;

  fvdbg("startpage: %08lx npages: %d\n", (long)startpage, (int)npages);
  DEBUGASSERT(nand && nand->raw);

  /* Retrieve the model */

  raw    = nand->raw;
  model  = &raw->model;

  /* Get the number of pages in one block, the size of one page, and
   * the number of blocks on the device.
   */

  pagesperblock = nandmodel_pagesperblock(model);
  pagesize      = nandmodel_getpagesize(model);
  maxblock      = nandmodel_getdevblocks(model);

  /* Get the block and page offset associated with the startpage */

  block = startpage / pagesperblock;
  page  = startpage % pagesperblock;

  /* Lock access to the NAND until we complete the write */

  nand_lock(nand);

  /* Then write every page into NAND */

  for (remaining = npages; remaining > 0; remaining--)
    {
      /* Check for attempt to write beyond the end of NAND */

      if (block > maxblock)
        {
          fdbg("ERROR: Write beyond the end of FLASH, block=%ld\n",
               (long)block);

          ret = -ESPIPE;
          goto errout_with_lock;
        }

      /* Write the next page into NAND */

      ret = nand_writepage(nand, block, page, buffer);
      if (ret < 0)
        {
          fdbg("ERROR: nand_writepage failed block=%ld page=%d: %d\n",
               (long)block, page, ret);
          goto errout_with_lock;
        }

      /* Increment the page number.  If we exceed the number of
       * pages per block, then reset the page number and bump up
       * the block number.
       */

      if (++page >= pagesperblock)
        {
          page = 0;
          block++;
        }

      /* Increment the buffer point by the size of one page */

      buffer += pagesize;
    }

  nand_unlock(nand);
  return npages;

errout_with_lock:
  nand_unlock(nand);
  return ret;
}

/****************************************************************************
 * Name: nand_ioctl
 ****************************************************************************/

static int nand_ioctl(struct mtd_dev_s *dev, int cmd, unsigned long arg)
{
  FAR struct nand_dev_s *nand = (FAR struct nand_dev_s *)dev;
  FAR struct nand_raw_s *raw;
  FAR struct nand_model_s *model;
  int ret = -EINVAL; /* Assume good command with bad parameters */

  DEBUGASSERT(nand && nand->raw);
  raw   = nand->raw;
  model = &raw->model;

  switch (cmd)
    {
      case MTDIOC_GEOMETRY:
        {
          struct mtd_geometry_s *geo = (struct mtd_geometry_s *)arg;
          if (geo)
            {
              /* Populate the geometry structure with information needed to know
               * the capacity and how to access the device.  Returns:
               *
               *   blocksize    Size of one read/write block in bytes
               *   erasesize    Size of one erase block in bytes
               *   neraseblocks The number of erase blocks in the device
               */

              geo->blocksize    = model->pagesize;
              geo->erasesize    = nandmodel_getbyteblocksize(model);
              geo->neraseblocks = nandmodel_getdevblocks(model);
              ret               = OK;
          }
        }
        break;

      case MTDIOC_BULKERASE:
        {
          /* Erase the entire device */

          ret = nand_erase(dev, 0, nandmodel_getdevblocks(model));
        }
        break;

      case MTDIOC_XIPBASE:
      default:
        ret = -ENOTTY; /* Bad command */
        break;
    }

  return ret;
}

/****************************************************************************
 * Public Functions
 ****************************************************************************/

/****************************************************************************
 * Name: nand_initialize
 *
 * Description:
 *   Probe and initialize NAND.
 *
 * Input parameters:
 *   raw      - Lower-half, raw NAND FLASH interface
 *   cmdaddr  - NAND command address base
 *   addraddr - NAND address address base
 *   dataaddr - NAND data address
 *   model    - A pointer to the model data (probably in the raw MTD
 *              driver instance.
 *
 * Returned value.
 *   A non-NULL MTD driver intstance is returned on success.  NULL is
 *   returned on any failaure.
 *
 ****************************************************************************/

FAR struct mtd_dev_s *nand_initialize(FAR struct nand_raw_s *raw)
{
  FAR struct nand_dev_s *nand;
  struct onfi_pgparam_s onfi;
  int ret;

  fvdbg("cmdaddr=%p addraddr=%p dataaddr=%p\n",
        (FAR void *)raw->cmdaddr, (FAR void *)raw->addraddr,
        (FAR void *)raw->dataaddr);

  /* Check if there is NAND connected on the EBI */

  if (!onfi_ebidetect(raw->cmdaddr, raw->addraddr, raw->dataaddr))
    {
      fdbg("ERROR: No NAND device detected at: %p %p %p\n",
           (FAR void *)raw->cmdaddr, (FAR void *)raw->addraddr,
           (FAR void *)raw->dataaddr);
      return NULL;
    }

  /* Read the ONFI page parameters from the NAND device */

  ret = onfi_read(raw->cmdaddr, raw->addraddr, raw->dataaddr, &onfi);
  if (ret < 0)
    {
      uint32_t chipid;

      fvdbg("Failed to get ONFI page parameters: %d\n", ret);

      /* If the ONFI model is not supported, determine the NAND
       * model from a lookup of known FLASH parts.
       */

      chipid = nand_chipid(raw);
      if (nandmodel_find(g_nandmodels, NAND_NMODELS, chipid,
                         &raw->model))
       {
          fdbg("ERROR: Could not determine NAND model\n");
          return NULL;
        }
    }
  else
    {
      FAR struct nand_model_s *model = &raw->model;
      uint64_t size;

      fvdbg("Found ONFI compliant NAND FLASH\n");

      /* Construct the NAND model structure */

      model->devid     = onfi.manufacturer;
      model->options   = onfi.buswidth ? NANDMODEL_DATAWIDTH16 : NANDMODEL_DATAWIDTH8;
      model->pagesize  = onfi.pagesize;
      model->sparesize = onfi.sparesize;

      size             = (uint64_t)onfi.pagesperblock *
                         (uint64_t)onfi.blocksperlun *
                         (uint64_t)onfi.pagesize;

      DEBUGASSERT(size < ((uint64_t)1 << 36));
      model->devsize   = (uint16_t)(size >> 20);

      size             = (uint64_t)onfi.pagesperblock *
                         (uint64_t)onfi.pagesize;

      DEBUGASSERT(size < ((uint64_t)1 << 26));
      model->blocksize = (uint16_t)(size >> 10);

      switch (onfi.pagesize)
        {
          case 256:
            model->scheme = &g_nand_sparescheme256;
            break;

          case 512:
            model->scheme = &g_nand_sparescheme512;
            break;

          case 2048:
            model->scheme = &g_nand_sparescheme2048;
            break;

          case 4096:
            model->scheme = &g_nand_sparescheme4096;
            break;
        }

      /* Disable any internal, embedded ECC function */

      (void)onfi_embeddedecc(&onfi, cmdaddr, addraddr, dataaddr, false);
    }

  /* Allocate an NAND MTD device structure */

  nand = (FAR struct nand_dev_s *)kmm_zalloc(sizeof(struct nand_dev_s));
  if (!nand)
    {
      fdbg("ERROR: Failed to allocate the NAND MTD device structure\n");
      return NULL;
    }

  /* Initialize the NAND MTD device structure */

  nand->mtd.erase  = nand_erase;
  nand->mtd.bread  = nand_bread;
  nand->mtd.bwrite = nand_bwrite;
  nand->mtd.ioctl  = nand_ioctl;
  nand->raw        = raw;

  sem_init(&nand->exclsem, 0, 1);

  /* Scan the device for bad blocks */

  (void)nand_devscan(nand);

  /* Return the implementation-specific state structure as the MTD device */

  return &nand->mtd;
}