1. MTD设备与底层驱动的关系

  MTD设备是一种特殊的抽象设备,它用于简化驱动开发。它是底层硬件和上层软件的桥梁,无论对Nand Flash或是Nor Flash,它都提供了统一的框架供上层文件系统使用。对于底层驱动,只需按照各自硬件差异实现MTD中要求的接口即可。
  MTD设备同时实现了Nand驱动的通用访问流程,对于上层文件系统的读、写等访问,MTD设备中都封装了接口,对于Nand驱动的编写,就是根据对应流程填充具体函数即可。

2. MTD结构分析

2.1 MTD中注册的接口函数

  为了能够实现上层文件系统的读写操作,MTD设备为Nand注册了如程序清单 2.1所示的接口。
                  程序清单 2.1 MTD结构

mtd->_erase             = nand_erase;                  /*  Nand擦除操作          */
mtd->_point             = NULL;                         /*  针对片上执行介质        */
mtd->_unpoint           = NULL;                         /*  针对片上执行介质        */
mtd->_read              = nand_read;                   /*  Nand读取操作           */
mtd->_write             = nand_write;                  /*  Nand写入操作           */
mtd->_read_oob          = nand_read_oob;              /*  Nand读取OOB区域      */
mtd->_write_oob         = nand_write_oob;             /*  Nand写入OOB区域      */
mtd->_sync              = nand_sync;                   /*  Nand同步               */
mtd->_lock              = NULL;                         /*  针对支持设备锁的设备    */
mtd->_unlock            = NULL;                         /*  针对支持设备锁的设备    */
mtd->_block_isbad       = nand_block_isbad;           /*  Nand坏块检查           */
mtd->_block_markbad      = nand_block_markbad;        /* Nand坏块标记            */

  对于Nand驱动编写,编程者需要了解nand_erase、nand_read、nand_write等接口的调用流程,才能明白Nand驱动的编程方法。

2.2 Nand驱动接口函数

  对应MTD中的接口,在Nand驱动中最终需实现如程序清单 2.2所示的各类接口函数。
                    程序清单 2.2 nand_chip结构

struct nand_chip {
    void __iomem *IO_ADDR_R;
    void __iomem *IO_ADDR_W;

    uint8_t (*read_byte)(struct mtd_info *mtd);
    u16      (*read_word)(struct mtd_info *mtd);
    void     (*write_buf)(struct mtd_info *mtd, const uint8_t *buf, int len);
    void     (*read_buf)(struct mtd_info *mtd, uint8_t *buf, int len);   
    void     (*select_chip)(struct mtd_info *mtd, int chip);      
    int      (*dev_ready)(struct mtd_info *mtd);
    void     (*cmdfunc)(struct mtd_info *mtd, unsigned command, int column,
            int page_addr);
    int      (*waitfunc)(struct mtd_info *mtd, struct nand_chip *this);
    void     (*erase_cmd)(struct mtd_info *mtd, int page);
    int      (*write_page)(struct mtd_info *mtd, struct nand_chip *chip,
            const uint8_t *buf, int oob_required, int page,
            int cached, int raw);
    ……
    struct nand_ecc_ctrl ecc;
}

  nand_chip中的接口并非要求全部实现,对应每一个函数在SylixOS的Base工程中都有默认实现,Nand驱动所做的是将这些接口中与默认实现不对应的单独实现。
  对于Nand中所需的ECC校验操作,本文不做描述,实际调试时可设置为“软件ECC”,待Nand功能实现后,可再修改为实际的硬件ECC操作。

2.3 驱动注册

  以STM32为例,Nand驱动中的nand_chip结构体大致的实现如程序清单 2.3所示。
                  程序清单 2.3 STM32的Nand驱动实现

pNandChip->IO_ADDR_R        = (VOID *)NAND_ADDRESS;
pNandChip->IO_ADDR_W     = (VOID *)NAND_ADDRESS;
pNandChip->cmdfunc               = __nandCommand;           /*  命令操作函数                    */
pNandChip->dev_ready           = __nandDevReady;         /*  判断设备是否准备好        */
pNandChip->select_chip         = __nandChipSelect;         /*  Nand片选操作                   */
pNandChip->waitfunc               = __nandWaitForReady;     /*  判断设备操作是否结束    */
pNandChip->chip_delay          = 50;
pNandChip->ecc.mode            = NAND_ECC_SOFT;

  因为STM32的read_byte、read_word、write_buf等接口与SylixOS中Base实现一致,所以此部分无需在Nand驱动中再次实现。
  对于STM32的Nand驱动,ECC采用了软件ECC方式。

3.流程分析

3.1 复位流程

3.1.1 系统调用流程

  Nand驱动初始时会调用nand_scan接口,nand_scan接口的调用关系如图 3.1所示。最终会调用到Nand驱动实现的cmdfunc函数,并传递NAND_CMD_RESET参数。
                SylixOS中MTD调用底层接口流程分析_第1张图片
                  图 3.1 Nand驱动复位流程

3.1.2 Nand驱动实现

  由系统调用关系可知,在__nandCommand中需实现如程序清单 3.1所示的逻辑,HAL_NAND_Reset为STM32具体的复位操作。
                程序清单 3.1 STM32的Nand复位实现

switch (uiCommand) {
case NAND_CMD_RESET:                                /*  nand复位操作                */
    HAL_NAND_Reset(&_G_handleType);
return;
……
}

3.2 ID读取流程

3.2.1 系统调用流程

  Nand驱动初始时调用nand_scan接口读取Nand的ID,其调用关系如图 3.2所示。
                 SylixOS中MTD调用底层接口流程分析_第2张图片
                  图 3.2 Nand读取ID流程
  nand_scan会调用到Nand驱动实现的cmdfunc函数,并传递NAND_CMD_READID参数,之后调用read_byte函数,读取到Nand的ID。

3.2.2 Nand驱动实现

  由系统调用关系可知,在__nandCommand中需实现如程序清单 3.2所示的逻辑,根据STM32寄存器说明,实现STM32读取Nand ID的具体操作。
                  程序清单 3.2 STM32的Nand读取ID

switch (uiCommand) {
……
case NAND_CMD_READID:                                /*  nand读取ID操作            */
    writeb(NAND_CMD_READID,     NAND_ADDRESS | NAND_CMD);
    writeb(0x00,                NAND_ADDRESS | NAND_ADDR);
return;
……
}

3.3 擦除流程

3.3.1 系统调用流程

  MTD设备擦除时调用nand_erase接口,其调用关系如图 3.3所示,nand_erase会判断是否是单块擦除。
  如果是单块擦除,则调用single_erase_cmd,该接口会调用cmdfunc函数,依次传递NAND_CMD_ERASE1参数和NAND_CMD_ERASE2参数,之后调用waitfunc等待擦除操作完成;
  如果是多块擦除,则调用multi_erase_cmd,该接口会调用四次cmdfunc函数,传递NAND_CMD_ERASE1参数,再调用一次cmdfunc函数,传递NAND_CMD_ERASE2参数,之后调用waitfunc等待擦除操作完成。
        SylixOS中MTD调用底层接口流程分析_第3张图片
                  图 3.3 Nand擦除流程

3.3.2 Nand驱动实现

  由系统调用关系可知,在__nandCommand中需实现如程序清单 3.3所示的逻辑,根据STM32寄存器说明,实现STM32擦除Nand的具体操作。
                程序清单 3.3 STM32的Nand擦除操作

switch (uiCommand) {
……
case NAND_CMD_ERASE2:
    writeb(NAND_CMD_ERASE2,             NAND_ADDRESS | NAND_CMD);
return;
case NAND_CMD_ERASE1:
    writeb(NAND_CMD_ERASE1,             NAND_ADDRESS | NAND_CMD);
    writeb((UINT8)(iPagAddr),           NAND_ADDRESS | NAND_ADDR);
    writeb((UINT8)(iPagAddr >>  8),  NAND_ADDRESS | NAND_ADDR);
    writeb((UINT8)(iPagAddr >> 16),  NAND_ADDRESS | NAND_ADDR);
return;
……
}

  同时实现__nandWaitForReady接口,如程序清单 3.4所示。
            程序清单 3.4 STM32的Nand等待操作完成操作

static INT  __nandWaitForReady (struct mtd_info  *pMtd, struct nand_chip *this)
{
    UINT32 uiTime  = 0;

    while (1) {
        if (__nandDevReady(pMtd)) {                  /*  如果已经准备好              */
            break;
        }

        uiTime++;
        if(uiTime >= MAX_WAIT_TIME) {
            return  (LW_FALSE);                     /*  超时                             */
        }
    }

    return  (LW_TRUE);                              /*  准备好                          */
}

3.4 读取流程

3.4.1 系统调用流程

  MTD设备调用nand_read接口读取Nand的数据,其调用关系如图 3.4所示。
               SylixOS中MTD调用底层接口流程分析_第4张图片
                  图 3.4 Nand读取流程
  nand_read中首先会调用cmd_func,传递NAND_CMD_READ0参数,之后调用read_buf实现,实际进行数据读取。

3.4.2 Nand驱动实现

  由系统调用关系可知,在__nandCommand中需实现如程序清单 3.5所示的逻辑,根据STM32寄存器说明,首先对读取Nand的地址进行设置。
              程序清单 3.5 STM32的Nand读取地址设置

switch (uiCommand) {
……
case NAND_CMD_READ0:
    writeb(NAND_CMD_READ0,              NAND_ADDRESS | NAND_CMD);
    writeb((UINT8)(iColumn),            NAND_ADDRESS | NAND_ADDR);
    writeb((UINT8)(iColumn >>   8),      NAND_ADDRESS | NAND_ADDR);
    writeb((UINT8)(iPagAddr),            NAND_ADDRESS | NAND_ADDR);
    writeb((UINT8)(iPagAddr >>  8),   NAND_ADDRESS | NAND_ADDR);
    writeb((UINT8)(iPagAddr >> 16),   NAND_ADDRESS | NAND_ADDR);
    writeb(NAND_CMD_READSTART,          NAND_ADDRESS | NAND_CMD);
    __nandWaitRB(1);                                /*  等待RB脚变为高电平          */
return;
……
}

之后调用的read_buf函数,可以使用SylixOS的Base中的默认实现。

3.5 写入流程

3.5.1 系统调用流程

  MTD设备写入时调用nand_write接口写入数据至Nand,其调用关系如图 3.5所示。
                SylixOS中MTD调用底层接口流程分析_第5张图片
                  图 3.5 Nand写入流程

3.5.2 Nand驱动流程

  由系统调用关系可知,在__nandCommand中需实现如程序清单 3.6所示的逻辑,根据STM32寄存器说明,首先对写入Nand的地址进行设置。
              程序清单 3.6 STM32的Nand写入地址设置

switch (uiCommand) {
……
case NAND_CMD_SEQIN:
    writeb(NAND_CMD_WRITE0,             NAND_ADDRESS | NAND_CMD);
    writeb((UINT8)(iColumn),             NAND_ADDRESS | NAND_ADDR);
    writeb((UINT8)(iColumn  >>  8),   NAND_ADDRESS | NAND_ADDR);
    writeb((UINT8)(iPagAddr),           NAND_ADDRESS | NAND_ADDR);
    writeb((UINT8)(iPagAddr >>  8),  NAND_ADDRESS | NAND_ADDR);
    writeb((UINT8)(iPagAddr >> 16),  NAND_ADDRESS | NAND_ADDR);
    bspDelayNs(100);
    return;
……
}

  之后调用的write_buf函数,可以使用SylixOS的Base中的默认实现。

3.6 读取OOB

3.6.1 系统调用流程

             SylixOS中MTD调用底层接口流程分析_第6张图片
                图 3.6 Nand读取OOB流程
  如图 3.6所示,Nand读取OOB的流程与Nand读取操作基本相同,只是cmd_func传递的命令为NAND_CMD_READOOB,所以在Nand驱动中需要进行实现。

3.6.2 Nand驱动流程

  在Nand驱动中的_nandCommand中需要响应NAND_CMD_READOOB操作,如程序清单 3.7所示。
            程序清单 3.7 STM32的Nand读取OOB地址的调整

if (uiCommand == NAND_CMD_READOOB) {            /* 模拟 NAND_CMD_READOOB        */
    iColumn  += pMtd->writesize;
    uiCommand = NAND_CMD_READ0;
}

3.7 写入OOB

3.7.1 系统调用流程

  如图 3.7所示,Nand写入OOB的流程与Nand写入操作基本相同,只是在写完数据后会调用cmd_func,传递NAND_CMD_PAGEPROG命令。
                SylixOS中MTD调用底层接口流程分析_第7张图片
                  图 3.7 Nand写入OOB流程

3.7.2 Nand驱动流程

  在Nand驱动中的_nandCommand中需要响应NAND_CMD_PAGEPROG操作,如程序清单 3.8所示。
            程序清单 3.8 STM32的Nand写入OOB的页编程命令

switch (uiCommand) {
……
case NAND_CMD_PAGEPROG:
    writeb(NAND_CMD_PAGEPROG,       NAND_ADDRESS | NAND_CMD);
    return;
……
}