开发环境

  1. 开发环境

    宿主机: Windows7 64bits 系统

    开发板: 安米MDK972

    软件环境: RealEvo-IDE3.0

    NAND Flash: S34ML02G100TF100

  2. S34ML02G100TF100芯片参数

  • Density:2 Gbit

  • Input / Output Bus Width: 8-bits

  • Page Size:2112 (2048 + 64) bytes; 64 bytes is spare area

  • Block Size: 64 Pages;128k + 4k bytes

  • Plane Size: 1024 Blocks per Plane;128M + 4M bytes

  • Device Size: 2 Planes per Device or 256 Mbyte

NAND控制器结构

NUC970的NAND控制器包含在FMI中。FMI分为DMA单元和FMI单元。对于NAND,支持单一DMA通道和硬件ECC,如图 2-1所示。

SylixOS下基于NUC970的NAND驱动

2-1   NUC970 NAND控制器

 

技术实现

  1. 驱动框架

    SylixOS中NAND Flsh的驱动框架如图 3-1所示。NAND通用驱动主要在fs/mtd/nand/nand_base.c中,该文件包含了NAND的通用操作。驱动工程师需要在NAND通用驱动的基础上实现与硬件相关的驱动层的结构体(nand_chip),该结构体包含了对具体硬件相关的控制和操作函数,以及相关硬件参数和配置信息。MTD层与文件系统,SylixOS已经完全实现,不需要驱动工程师实现。

    SylixOS下基于NUC970的NAND驱动

    3-1   NAND驱动框架

  2. 框架实现

    NAND驱动需要完成NAND控制器、ECC的配置以及NAND的相关操作函数及文件系统挂载,如果使用硬件ECC一般自己定义OOB布局。NUC970驱动实现的操作如程序清单 3-1所示。

    程序清单 3-1   NAND实现框架

        nandchipNand->cmd_ctrl        = hwControl;
        nandchipNand->cmdfunc         = nandCommand;
        nandchipNand->dev_ready       = devReady;
        nandchipNand->select_chip     = chipSelect;
        nandchipNand->read_byte       = nandReadByte;
        nandchipNand->write_buf       = nandWriteBuf;
        nandchipNand->read_buf        = nandReadBuf;
        nandchipNand->chip_delay      = 50;
        nandchipNand->ecc.mode        = NAND_ECC_HW_OOB_FIRST;
        nandchipNand->ecc.hwctl       = nandEnableHwEcc;
        nandchipNand->ecc.calculate   = nandCalculateEcc;
        nandchipNand->ecc.correct     = nandCorrectData;
        nandchipNand->ecc.write_page  = nandWritePageHwEcc;
        nandchipNand->ecc.read_page   = nandReadPageHwEccOobFirst;
        nandchipNand->ecc.read_oob    = nandReadoobHwEcc;
    nandchipNand->ecc.layout      = &__Gpnuc970nandoob;

     

  3. 控制器初始化

    控制器初始化主要实现了模块时钟使能、管脚复用、时序设置、片选、解除写保护、页大小、软件复位等操作。

  4. ECC配置

    ECC配置主要设置冗余区大小,保护前3字节,自动写校验值到NAND,设置算法等级,ECC使能等操作。

  5. 函数cmd_ctrl

    该函数主要实现对ALE/CLE/nCE的控制,同时用来写命令和地址。

    程序清单 3-2  命令控制函数

     

    static VOID hwControl (struct mtd_info  *pMtd, INT  iCmd, UINT  uiCtrl)
    {
        struct nand_chip *pChip = pMtd->priv;
    
        if (uiCtrl & NAND_CTRL_CHANGE) {
            ULONG IO_ADDR_W = (ULONG)REG_NANDDATA;
    
            if ((uiCtrl & NAND_CLE)) {
                IO_ADDR_W = REG_NANDCMD;
            }
            if ((uiCtrl & NAND_ALE)) {
                IO_ADDR_W = REG_NANDADDR;
            }
    
            pChip->IO_ADDR_W = (VOID *)IO_ADDR_W;
        }
    
        if (iCmd != NAND_CMD_NONE) {
            writeb(iCmd, pChip->IO_ADDR_W);
        }
    }
  6. 函数cmdfunc

    该函数主要实现向芯片中写命令的功能,在系统提供的默认函数中通过调用cmd_ctrl函数来实现具体写操作。由于NUC970的控制器需在最后一个地址周期手动设置EOA位,无法使用默认函数,差异代码如程序清单 3-3所示:

    程序清单 3-3   命令功能函数差异代码

    writel((iColumn >> BUS_WIDTH) | NANDADDR_EOA, REG_NANDADDR);

     

  7. 函数dev_ready

    该函数主要用来获得设备ready/busy引脚状态。如果该函数指针设置为NULL无法获得ready/busy引脚状态,则ready/busy信息需要通过读取NAND芯片的状态寄存器。代码实现如程序清单 3-4所示。

    程序清单 3-4  获得NAND状态

        return ((readl(REG_NANDINTSTS) & NANDINTSTS_RB0_Status) ? 1 : 0);

     

  8. 函数read_byte

    该函数功能为从NAND芯片读取一个字节,代码如程序清单 3-5所示。

    程序清单 3-5  NAND读一个字节

     

        return ((UCHAR)readl(REG_NANDDATA));

     

  9. 函数write_buf

    该函数功能为从一个缓冲区写数数据到NAND芯片。代码实现如程序清单 3-6。

    程序清单 3-6  写缓冲区数据到NAND

     

        for (i = 0; i < iLen; i++) {
            writel(pucbuf[i], REG_NANDDATA);
        }

     

  10. 函数read_buf

    该函数功能为从NAND芯片读数据到一个缓冲区。代码实现如程序清单 3-7所示。

    程序清单 3-7  读数据到缓冲区

     

        for (i = 0; i < iLen; i++) {
            writel(pucbuf[i], REG_NANDDATA);
        }

     

  11. 函数ecc.hwctl

    该函数用于控制硬件ECC发生器,只有在使用硬件ECC时实现。本例的硬件校验在传输中实现,因此该函数为空实现。

  12. 函数ecc.calculate

    该函数用于ECC计算,或从ECC硬件中读回。本例的硬件校验在传输中实现,因此该函数为空实现。

  13. 函数ecc.correct

    该函数用于ECC校正。本例的硬件校验在传输中实现,因此该函数为空实现。

  14. 函数ecc.write_page

    该函数主要实现带ECC的写一页数据到NAND芯片。在传输的过程中,ECC电路会自动计算ECC校验值,并存储到控制器分配的寄存器组中。完成传输后寄存器组中的OOB数据会根据设置自动写进NAND芯片。实现流程如程序清单 3-8所示。

    程序清单 3-8  带硬件ECC的写页

    static INT nandWritePageHwEcc (struct mtd_info   *pMtd,
                                   struct nand_chip  *pChip,
                                   const UCHAR       *pucBuf,
                                   INT                iOobRequired)
    {
        UCHAR          *pucEccCalc   = pChip->buffers->ecccalc;
        UINT             uiEccBytes  = pChip->ecc.layout->eccbytes;
        register CHAR     *pcPtr     = (CHAR *)REG_NANDRA0;
    
        memset((VOID *)pcPtr, 0xFF, pMtd->oobsize);
        memcpy((VOID *)pcPtr, (VOID *)pChip->oob_poi,  pMtd->oobsize - pChip->ecc.total);
    
        nandDmaTransfer(pMtd, pucBuf, pMtd->writesize , 0x1);
    
        /*
         *  Copy parity code in SMRA to calc
         */
        memcpy((VOID *)pucEccCalc,
               (VOID *)(REG_NANDRA0 + (pMtd->oobsize - pChip->ecc.total)),
               pChip->ecc.total);
    
        /*
         *  Copy parity code in calc to oob_poi
         */
        memcpy((VOID *)(pChip->oob_poi + uiEccBytes),
                (VOID *)pucEccCalc,
                pChip->ecc.total);
    
        return 0;
    }


     

  15. 函数ecc.read_page

    该函数主要实现带ECC校验的从NAND芯片读出一页数据。本例为硬件ECC,需要先读出OOB区数据到控制器分配的寄存器组中。在数据传输的过程中,ECC电路会计算ECC校验值,并与寄存器组中的值比较,检查是否产生错误,以及定位和计算校错值。若产生错误,程序需要根据错误位置和错误值进行校错。具体流程如程序清单 3-9所示:

    程序清单 3-9  ECC的读页

     

    static INT nandReadPageHwEccOobFirst (struct mtd_info   *pMtd,
                                          struct nand_chip  *pChip,
                                          UCHAR             *ucBuf,
                                          INT                iOobRequired,
                                          INT                iPage)
    {
        INT      iEccSize = pChip->ecc.size;
        CHAR    *pcPtr    = (CHAR *)REG_NANDRA0;
    
        /*
         *  At first, read the OOB area
         */
        nandCommand(pMtd, NAND_CMD_READOOB, 0, iPage);
        nandReadBuf(pMtd, pChip->oob_poi, pMtd->oobsize);
    
        /*
         *  Second, copy OOB data to SMRA for page read
         */
        memcpy((VOID *)pcPtr, (VOID *)pChip->oob_poi, pMtd->oobsize);
    
        /*
         *  Third, read data from nand
         */
        nandCommand(pMtd, NAND_CMD_READ0, 0, iPage);
        nandDmaTransfer(pMtd, ucBuf, iEccSize, 0x0);
    
        /*
         *  Fouth, restore OOB data from SMRA
         */
        memcpy((VOID *)pChip->oob_poi, (VOID *)pcPtr, pMtd->oobsize);
    
        return 0;
    }

     

  16. 函数ecc.read_oob

    该函数主要实现从芯片中读取OOB数据。实现流程如程序清单 3-10所示。

    程序清单 3-10  带硬件ECC的读OOB区数据

     

    static INT nandReadoobHwEcc(struct mtd_info  *pMtd, struct nand_chip  *pChip, INT  iPage)
    {
        CHAR *cPtr = (char *)REG_NANDRA0;
    
        /*
         *  At first, read the OOB area
         */
        nandCommand(pMtd, NAND_CMD_READOOB, 0, iPage);
        nandReadBuf(pMtd, pChip->oob_poi, pMtd->oobsize);
    
        /*
         *  Second, copy OOB data to SMRA for page read
         */
        memcpy ((VOID *)cPtr, (VOID *)pChip->oob_poi, pMtd->oobsize);
    
        return 0;
    }

     

  17. ecc.layout

    nand_ecc为ECC布局控制结构体。通过该结构体配置OOB区中ECC的位数和位置,可用位数和空闲位数。本例通过调用程序清单 3-11代码实现OOB区的布局控制。

    程序清单 3-11   OOB区布局

     

    static VOID oobTableLayout ( struct nand_ecclayout  *pNandOOBTbl, INT  iOobSize , INT  iEccBytes)
    {
        pNandOOBTbl->eccbytes     = iEccBytes;
    
        pNandOOBTbl->oobavail     = iOobSize - DEF_RESERVER_OOB_SIZE_FOR_MARKER - iEccBytes ;
    
        pNandOOBTbl->oobfree[0].offset = DEF_RESERVER_OOB_SIZE_FOR_MARKER;     /*  Bad block marker size    */
    
        pNandOOBTbl->oobfree[0].length = iOobSize - iEccBytes - pNandOOBTbl->oobfree[0].offset ;
    }

     

  18. 文件系统挂载

    在SylixOS下NAND Flash通常挂载YAFFS文件系统,并分为n0和n1分区,其中n0分区用作启动分区,n1作为应用分区。挂载流程如程序清单 3-12所示。

    程序清单 3-12  文件系统挂挂载

     

        yaffs_mtd_drv_install(&__GyaffsdevBootDev);
        yaffs_mtd_drv_install(&__GyaffsdevCommDev);
    
        yaffs_add_device(&__GyaffsdevBootDev);                              /* add to yaffs device table    */
        yaffs_add_device(&__GyaffsdevCommDev);                              /* add to yaffs device table    */
    
        yaffs_mount(cBootDevName);
        yaffs_mount(cCommDevName);

     

参考资料

无。