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参数。
图 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所示。
图 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等待擦除操作完成。
图 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所示。
图 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所示。
图 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 系统调用流程
图 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命令。
图 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;
……
}