下面介绍s3c2440的nandflash控制器。s3c2440支持8位或16位的每页大小为256字,512字节,1K字和2K字节的nandflash,这些配置是通过系统上电后相应引脚的高低电平来实现的。s3c2440还可以硬件产生ECC校验码,这为准确及时发现nandflash的坏块带来了方便。nandflash控制器的主要寄存器有NFCONF(nandflash配置寄存器),NFCONT(nandflash控制寄存器),NFCMMD(nandflash命令集寄存器),NFADDR(nandflash地址集寄存器),NFDATA(nandflash数据寄存器),NFMECCD0/1(nandflash的main区ECC寄存器),NFSECCD(nandflash的spare区ECC寄存器),NFSTAT(nandflash操作状态寄存器),NFESTAT0/1(nandflash的ECC状态寄存器),NFMECC0/1(nandflash用于数据的ECC寄存器),以及NFSECC(nandflash用于IO的ECC寄存器)。
NFCMMD,NFADDR和NFDATA分别用于传输命令,地址和数据,为了方便起见,我们可以定义一些宏定义用于完成上述操作:
#define NF_CMD(data) {rNFCMD = (data); } //传输命令
#define NF_ADDR(addr) {rNFADDR = (addr); } //传输地址
#define NF_RDDATA() (rNFDATA) //读32位数据
#define NF_RDDATA8() (rNFDATA8) //读8位数据
#define NF_WRDATA(data) {rNFDATA = (data); } //写32位数据
#define NF_WRDATA8(data) {rNFDATA8 = (data); } //写8位数据
其中rNFDATA8的定义为(*(volatile unsigned char *)0x4E000010) //0x4E000010此地址是NFDATA寄存器的地址
NFCONF主要用到了TACLS、TWRPH0、TWRPH1,这三个变量用于配置nandflash的时序。s3c2440的数据手册没有详细说明这三个变量的具体含义,但通过它所给出的时序图,我们可以看出,TACLS为CLE/ALE有效到nWE有效之间的持续时间,TWRPH0为nWE的有效持续时间,TWRPH1为nWE无效到CLE/ALE无效之间的持续时间,这些时间都是以HCLK为单位的(本文程序中的HCLK=100MHz)。通过查阅K9F2G08U0A的数据手册,我们可以找到并计算该nandflash与s3c2440相对应的时序:K9F2G08U0A中的tWP与TWRPH0相对应,tCLH与TWRPH1相对应,(tCLS-tWP)与TACLS相对应。K9F2G08U0A给出的都是最小时间,s3c2440只要满足它的最小时间即可,因此TACLS、TWRPH0、TWRPH1这三个变量取值大一些会更保险。在这里,这三个值分别取1,2和0。
NFCONF的第0位表示的是外接的nandflash是8位IO还是16位IO,这里当然要选择8位的IO。NFCONT寄存器是另一个需要事先初始化的寄存器。它的第13位和第12位用于锁定配置,第8位到第10位用于nandflash的中断,第4位到第6位用于ECC的配置,第1位用于nandflash芯片的选取,第0位用于nandflash控制器的使能。另外,为了初始化nandflash,还需要配置GPACON寄存器,使它的第17位到第22位与nandflash芯片的控制引脚相对应。下面的程序实现了初始化nandflash控制器:
void NF_Init ( void )
{
rGPACON = (rGPACON &~(0x3f<17)) | (0x3f<17); //配置芯片引脚
//TACLS=1、TWRPH0=2、TWRPH1=0,8位IO,
rNFCONF = (TACLS<12)|(TWRPH0<8)|(TWRPH1<4)|(0<0);
//非锁定,屏蔽nandflash中断,初始化ECC及锁定main区和spare区ECC,使能nandflash片选及控制器
rNFCONT = (0<13)|(0<12)|(0<10)|(0<9)|(0<8)|(1<6)|(1<5)|(1<4)|(1<1)|(1<0);
}
为了更好地应用ECC和使能nandflash片选,我们还需要一些宏定义:
#define NF_nFCE_L() {rNFCONT &= ~(1<1); }
#define NF_CE_L() NF_nFCE_L() //打开nandflash片选
#define NF_nFCE_H() {rNFCONT |= (1<1); }
#define NF_CE_H() NF_nFCE_H() //关闭nandflash片选
#define NF_RSTECC() {rNFCONT |= (1<4); } //复位ECC
#define NF_MECC_UnLock() {rNFCONT &= ~(1<5); } //解锁main区ECC
#define NF_MECC_Lock() {rNFCONT |= (1<5); } //锁定main区ECC
#define NF_SECC_UnLock() {rNFCONT &= ~(1<6); } //解锁spare区ECC
#define NF_SECC_Lock() {rNFCONT |= (1<6); } //锁定spare区ECC
NFSTAT是另一个比较重要的寄存器,它的第0位可以用于判断nandflash是否在忙,第2位用于检测RnB引脚信号:
#define NF_WAITRB() {while(!(rNFSTAT&(1<0)));} //等待nandflash不忙
#define NF_CLEAR_RB() {rNFSTAT |= (1<2); } //清除RnB信号
#define NF_DETECT_RB() {while(!(rNFSTAT&(1<2)));} //等待RnB信号变高,即不忙
下面就详细介绍K9F2G08U0A的基本操作,包括复位,读ID,页读、写数据,随意读、写数据,块擦除等。
复位操作最简单,只需写入复位命令即可:
static void rNF_Reset()
{
NF_CE_L(); //打开nandflash片选
NF_CLEAR_RB(); //清除RnB信号
NF_CMD(CMD_RESET); //写入复位命令
NF_DETECT_RB(); //等待RnB信号变高,即不忙
NF_CE_H(); //关闭nandflash片选
}
读取K9F2G08U0A芯片ID操作首先需要写入读ID命令,然后再写入0x00地址,就可以读取到一共五个周期的芯片ID,第一个周期为厂商ID,第二个周期为设备ID,第三个周期至第五个周期包括了一些具体的该芯片信息,这里就不多介绍: