1 Nand flash 工作原理
S3C2410 板的Nand Flash 支持由两部分组成:Nand Flash 控制器(集成在S3C2410 CPU)和Nand Flash 存储
芯片(K9F1208U0B)两大部分组成。当要访问Nand Flash中的数据时,必须通过Nand Flash控制器发送命
令才能完成。所以, Nand Flash相当于S3C2410的一个外设,而不位于它的内存地址区.
1.1 Nand flash 芯片工作原理
Nand flash 芯片型号为Samsung K9F1208U0B,数据存储容量为64MB ,采用块页式存储管理。8 个I/O
引脚充当数据、地址、命令的复用端口。
1.1.1 芯片内部存储布局及存储操作特点
一片Nand flash 为一个设备(device), 其数据存储分层为:
1设备(Device) = 4096 块(Blocks)
1块(Block) = 32 页/ (Pages/rows) ;页与 是相同的意思,叫法不一样
1块(Page) = 528 字节(Bytes) = 数据块大小(512Bytes) + OOB 块大小(16Bytes)
在每一页中,最后16 个字节 (又称OOB)用于Nand Flash 命令执行完后设置状态用,剩余512 个字节又
分为前半部分和后半部分。可以通过Nand Flash 命令00h/01h/50h 分别对前半部、后半部、OOB 进行定位通过
Nand Flash 内置的指针指向各自的首地址。
存储操作特点:
1. 擦除操作的最小单位是块。
2. Nand Flash 芯片每一位(bit)只能从1 变为0 ,而不能从0 变为 1,所以在对其进行写入操作之前要一定将相应
块擦除(擦除即是将相应块得位全部变为 1).
3. OOB 部分的第六字节(即517 字节)标志是否是坏块,如果不是坏块该值为FF ,否则为坏块。
4. 除OOB 第六字节外,通常至少把OOB 的前3 个字节存放Nand Flash 硬件ECC 码(关于硬件ECC 码请参看
Nandflash 控制器一节).
1.1.2 重要芯片引脚功能
I/O0-I/O7:复用引脚。可以通过它向nand flash 芯片输入数据、地址、nand flash 命令以及输出数据和操作
状态信息。
CLE(Command Latch Enable): 命令锁存允许
ALE(Address Lactch Enable): 地址锁存允许
-CE: 芯片选择
-RE: 读允许
-WE: 写允许
-WP: 在写或擦除期间,提供写保护
R/-B: 读/忙输出
1.1.3 寻址方式
Samsung K9F1208U0B Nand Flash 片内寻址采用26 位地址形式。从第0 位开始分四次通过I/O0 -I/O7 进
传送,并进行片内寻址。具体含义如下:
0 -7 位:字节在上半部、下半部及OOB 内的偏移地址
8 位:值为0 代表对一页内前256 个字节进行寻址
值为1 代表对一页内后256 个字节进行寻址
9-13位:对页进行寻址
14-25 位:对块进行寻址
当传送地址时,从位0 开始
1.1.4 Nand flash主要内设命令详细介绍
Nand Flash 命令执 是通过将命令字送到Nand Flash 控制器的命令寄存器来执 。
Nand Flash 的命令是分周期执行的,每条命令都有一个或多个执 周期,每个执 周期都有相映代码表示该周
期将要执行的动作。
主要命令有:Read 1 、Read 2 、Read ID 、Reset 、Page Program 、Block Erase 、Read Status 。
详细介绍如下:
1. Read 1:
功能:表示将要读取Nand flash 存储空间中一个页的前半部分,并且将内置指针定位到前半部分的第一个字节。
命令代码:00h
2. Read 2:
功能:表示将要读取Nand flash 存储空间中一个页的后半部分,并且将内置指针定位到后半部分的第一个字节。
命令代码:01h
3. Read ID:
功能:读取Nand flash 芯片的ID 号
命令代码:90h
4. Reset:
功能:重启芯片。
命令代码:FFh
5. Page Program:
功能:对页进行编程命令, 用于写操作。
命令代码:首先写入00h(A 区)/01h(B 区)/05h(C 区), 表示写入那个区; 再写入80h 开始编程模式(写入模式) ,接
下来写入地址和数据; 最后写入 10h 表示编程结束.
6. Block Erase
功能:块擦除命令。
命令代码:首先写入60h 进入擦写模式,然后输入块地址; 接下来写入D0h, 表示擦写结束.
7. Read Status
功能:读取内部状态寄存器值命令。
命令代码:70h
1.2 Nand Flash 控制器工作原理
对Nand Flash 存储芯片进行操作, 必须通过Nand Flash 控制器的专用寄存器才能完成。所以,不能对Nand
Flash 进 总线操作。而Nand Flash 的写操作也必须块方式进 。对Nand Flash 的读操作可以按字节读取。
1.2.1 Nand Flash控制器特性
1. 支持对Nand Flash 芯片的读、检验、编程控制
2. 如果支持从Nand Flash 启动, 在每次重启后自动将前Nand Flash 的前4KB 数据搬运到ARM 的内部RAM 中
3. 支持ECC 校验
1.2.2 Nand Flash控制器工作原理
Nand Flash 控制器在其专用寄存器区(SFR)地址空间中映射有属于自己的特殊功能寄存器,就是通过将Nand
Flash 芯片的内设命令写到其特殊功能寄存器中,从而实现对Nand flash 芯片读、检验和编程控制的。特殊功能
寄存器有:NFCONF 、NFCMD 、NFADDR 、NFDATA 、NFSTAT 、NFECC 。寄存详细说明见下一节。
1.3 Nand flash 控制器中特殊功能寄存器详细介绍
1. 配置寄存器(NFCONF)
功能:用于对Nand Flash 控制器的配置状态进行控制。
在地址空间中地址:0x4E000000,其中:
Bit15:Nand Flash 控制器使能位,置0 代表禁止Nand Flash 控制器,置 1 代表激活Nand Flash 控制器;
要想访问Nand Flash 芯片上存储空间,必须激活Nand Flash 控制器。在复位后该位自动置0 ,因此在初始化时
必须将该位置为 1 。
Bit12:初始化ECC 位,置1 为初始化ECC;置0 为不初始化ECC 。
Bit11:Nand Flash 芯片存储空间使能位,置0 代表可以对存储空间进行操作;置 1 代表禁止对存储空
间进行操作。在复位后,该位自动为 1 。
Bit10-8:TACLS 位。根据此设定CLE&ALE 的周期。TACLS 的值范围在0 -7 之间。
Bit6-4 、2-0 分别为:TWRPH0 、TWRPH1 位。设定写操作的访问周期。其值在0 -7 之间。
2. 命令寄存器(NFCMD)
功能:用于存放Nand flash 芯片内设的操作命令。
在地址空间中地址:0x4E000004 ,其中:
Bit0 -7:存放具体Nand flash 芯片内设的命令值。其余位保留以后用。
3. 地址寄存器(NFADDR)
功能:用于存放用于对Nand flash 芯片存储单元寻址的地址值。
在地址空间中地址:0x4E000008 ,其中:
Bit0 -7:用于存放地址值。因为本款Nand flash 芯片只有I/O0 -7 的地址/数据复用引脚且地址是四周
期每次8 位送入的,所以这里只用到8 位。其余位保留待用。
4. 数据寄存器(NFDATA)
功能:Nand flash 芯片所有内设命令执行后都会将其值放到该寄存器中。同时,读出、写入Nand flash
存储空间的值也是放到该寄存器。
在地址空间中地址:0x4E00000C ,其中:
Bit0 -7:用于存放需要读出和写入的数据。其余位保留代用。
5. 状态寄存器(NFSTAT)
功能:用于检测Nand flash 芯片上次对其存储空间的操作是否完成。
在地址空间中地址:0x4E000010 ,其中:
Bit0:置0 表示Nand flash 芯片正忙于上次对存储空间的操作;置 1 表示Nand flash 芯片准备好接收新
的对存储空间操作的请求。
6. ECC 校验寄存器(NFECC)
功能:ECC 校验寄存器
在地址空间中地址:0x4E000014 ,其中:
Bit0-Bit7: ECC0
Bit8-Bit15: ECC1
Bit16-Bit23: ECC2
1.4 Nand Flash 控制器中的硬件 ECC 介绍
1.4.1 ECC产生方法
ECC 是用于对存储器之间传送数据正确进行校验的一种算法,分硬件ECC 和软件ECC 算法两种,在
S3C2410 的Nand Flash 控制器中实现了由硬件电路 (ECC 生成器)实现的硬件ECC 。
1.4.2 ECC生成器工作过程
当写入数据到Nand flash 存储空间时, ECC 生成器会在写入数据完毕后自动生成ECC 码,将其放入到
ECC0 -ECC2 。当读出数据时Nand Flash 同样会在读数据完毕后,自动生成ECC 码将其放到ECC0 -ECC2 当
中。
1.4.3 ECC 的运用
当写入数据时,可以在每页写完数据后将产生的ECC 码放入到OOB 指定的位置(Byte 6)去,这样就完成了
ECC 码的存储。这样当读出该页数据时,将所需数据以及整个OOB 读出,然后将指定位置的ECC 码与读出数
据后在ECC0 -ECC1 的实际产生的ECC 码进 对比,如果相等则读出正确,若不相等则读取错误需要进行重
读。
2 在ADS 下 flash烧写程序
2.1 ADS 下flash烧写程序原理及结构
基本原理:在windows 环境下借助ADS 仿真器将在SDRAM 中的一段存储区域中的数据写到Nand flash 存
储空间中。烧写程序在纵向上分三层完成:
第一层: 主烧写函数 (完成将在SDRAM 中的一段存储区域中的数据写到Nand flash 存储空间中);
第二层: 为第一层主烧写函数提供支持的对Nand flash 进行操作的页读、写,块擦除等函数;
第三层:为第二层提供具体Nand flash 控制器中对特殊功能寄存器进行操作的核心函数,该层也是真正的
将数据能够在SDRAM 和Nand flash 之间实现传送的函数。
下面对其三层进行分述:
2.2 第三层实现说明
2.1.1 特殊功能寄存器定义
#define rNFCONF (*(volatile unsigned int *)0x4e000000)
#define rNFCMD (*(volatile unsigned char *)0x4e000004)
#define rNFADDR (*(volatile unsigned char *)0x4e000008)
#define rNFDATA (*(volatile unsigned char *)0x4e00000c)
#define rNFSTAT (*(volatile unsigned int *)0x4e000010)
#define rNFECC (*(volatile unsigned int *)0x4e000014)
#define rNFECC0 (*(volatile unsigned char *)0x4e000014)
#define rNFECC1 (*(volatile unsigned char *)0x4e000015)
#define rNFECC2 (*(volatile unsigned char *)0x4e000016)
2.1.2 操作的函数实现
1. 发送命令
#define NF_CMD(cmd) {rNFCMD=cmd;}
2. 写入地址
#define NF_ADDR(addr) {rNFADDR=addr;}
3. Nand Flash 芯片选中
#define NF_nFCE_L() {rNFCONF&=~(1<<11);}
4. Nand Flash 芯片不选中
#define NF_nFCE_H() {rNFCONF|=(1<<11);}
5. 初始化ECC
#define NF_RSTECC() {rNFCONF|=(1<<12);}
6. 读数据
#define NF_RDDATA() (rNFDATA)
7. 写数据
#define NF_WRDATA(data) {rNFDATA=data;}
8. 获取Nand Flash 芯片状态
#define NF_WAITRB() {while(!(rNFSTAT&(1<<0)));}
0/假: 表示Nand Flash 芯片忙状态
1/真:表示Nand Flash 已经准备好
2.3 第二层实现说明
2.3.1 Nand Flash 初始化
void NF_Init(void)
{ /* 设置Nand Flash 配置寄存器, 每一位的取值见1.3 节 */
rNFCONF=(1<<15)|(1<<14)|(1<<13)|(1<<12)|(1<<11)|(TACLS<<8)|(TWRPH0<<4)|(TWRPH1<<0);
/* 复位外部Nand Flash 芯片 */
NF_Reset();
}
2.3.2 Nand flash复位
static void NF_Reset(void)
{
int i;
NF_nFCE_L(); /* 片选Nand Flash 芯片*/
NF_CMD(0xFF); /* 复位命令 */
for(i=0;i<10;i++); /* 等待tWB = 100ns. */
NF_WAITRB(); /* wait 200~500us; */
NF_nFCE_H(); /* 取消Nand Flash 选中*/
}
2.3.3 获取 Nand flash ID
返回值为Nand flash 芯片的ID 号
unsigned short NF_CheckId(void)
{
int i;
unsigned short id;
NF_nFCE_L(); /* 片选Nand Flash 芯片*/
NF_CMD(0x90); /* 发送读ID 命令到Nand Flash 芯片 */
NF_ADDR(0x0); /* 指定地址0x0 ,芯片手册要求 */
for(i=0;i<10;i++); /* 等待tWB = 100ns. */
id=NF_RDDATA()<<8; /* 厂商ID(K9S1208V:0xec) */
id|=NF_RDDATA(); /* 设备ID(K9S1208V:0x76) */
NF_nFCE_H(); /* 取消Nand Flash 选中*/
return id;
}
2.3.4 Nand flash 写入
以页为单位写入.
参数说明:block 块号
page 页号
buffer 指向内存中待写入Nand flash 中的数据起始位置
返回值: 0 :写错误
1:写成功
static int NF_WritePage(unsigned int block, unsigned int page, unsigned char *buffer)
{
int i;
unsigned int blockPage = (block<<5)+page;
unsigned char *bufPt = buffer;
NF_RSTECC(); /* 初始化 ECC */
NF_nFCE_L(); /* 片选Nand Flash 芯片*/
NF_CMD(0x0); /* 从A 区开始写 */
NF_CMD(0x80); /* 写第一条命令 */
NF_ADDR(0); /* A0~A7 位(Column Address) */
NF_ADDR(blockPage&0xff); /* A9-A16, (Page Address) */
NF_ADDR((blockPage>>8)&0xff); /* A17-A24, (Page Address) */
NF_ADDR((blockPage>>16)&0xff); /* A25, (Page Address) */
for(i=0;i<512;i++)
{
NF_WRDATA(*bufPt++); /* 写一个页512 字节到Nand Flash 芯片 */
}
/*
* OOB 一共16 Bytes, 每一个字节存放什么由程序员自己定义, 通常,
* 我们在Byte0-Byte2 存ECC 检验码. Byte6 存放坏块标志.
*/
seBuf[0]=rNFECC0; /* 读取ECC 检验码0 */
seBuf[1]=rNFECC1; /* 读取ECC 检验码 1 */
seBuf[2]=rNFECC2; /* 读取ECC 检验码2 */
seBuf[5]=0xff; /* 非坏块标志 */
for(i=0;i<16;i++)
{
NF_WRDATA(seBuf[i]); /* 写该页的OOB 数据块 */
}
NF_CMD(0x10); /* 结束写命令 */
/* 等待Nand Flash 处于准备状态 */
for(i=0;i<10;i++);
NF_WAITRB();
/* 发送读状态命令 Nand Flash */
NF_CMD(0x70);
for(i=0;i<3;i++);
if (NF_RDDATA()&0x1)
{ /*如果写有错, 则标示为坏块 */
NF_nFCE_H(); /* 取消Nand Flash 选中*/
NF_MarkBadBlock(block);
return 0;
} else { /* 正常退出 */
NF_nFCE_H(); /* 取消Nand Flash 选中*/
return 1;
}
}
2.3.5 Nand flash读取
参数说明:block:块号
page:页号
buffer:指向将要读取到内存中的起始位置
返回值:1:读成功
0:读失败
static int NF_ReadPage(unsigned int block, unsigned int page, unsigned char *buffer)
{
int i;
unsigned int blockPage;
unsigned char ecc0, ecc1, ecc2;
unsigned char *bufPt=buffer;
unsigned char se[16];
page=page&0x1f;
blockPage=(block<<5)+page;
NF_RSTECC(); /* 初始化 ECC */
NF_nFCE_L(); /* 片选Nand Flash 芯片*/
NF_CMD(0x00); /* 从A 区开始读 */
NF_ADDR(0); /* A0~A7 位(Column Address) */
NF_ADDR(blockPage&0xff); /* A9-A16, (Page Address) */
NF_ADDR((blockPage>>8)&0xff); /* A17-A24, (Page Address) */
NF_ADDR((blockPage>>16)&0xff); /* A25, (Page Address) */
/* 等待Nand Flash 处于再准备状态 */
for(i=0;i<10;i++);
NF_WAITRB(); /*等待 tR(max 12us) */
/* 读整个页, 512 字节 */
for(i=0;i<512;i++)
{
*bufPt++=NF_RDDATA();
}
/* 读取ECC 码 */
ecc0=rNFECC0;
ecc1=rNFECC1;
ecc2=rNFECC2;
/* 读取该页的OOB 块 */
for(i=0;i<16;i++)
{
se[i]=NF_RDDATA();
}
NF_nFCE_H(); /* 取消Nand Flash 选中*/
/* 校验ECC 码, 并返回 */
if(ecc0==se[0] && ecc1==se[1] && ecc2==se[2])
return 1;
else
return 0;
}
2.3.6 Nand flash标记坏块
如果是坏块, 通过写OOB 块的Byte6 把该块标记为坏块。
参数说明:block 块号
返回值:1:ok ,成功完成标记。
0:表示写OOB 块正确.
static int NF_MarkBadBlock(unsigned int block)
{
int i;
unsigned int blockPage=(block<<5);
seBuf[0]=0xff;
seBuf[1]=0xff;
seBuf[2]=0xff;
seBuf[5]=0x44; /* 设置坏块标记 */
NF_nFCE_L(); /* 片选Nand Flash 芯片*/
NF_CMD(0x50); /* 从C 区开始写 */
NF_CMD(0x80); /* 发送编程命令, 让Nand Flash 处理写状态 */
NF_ADDR(0x0); /* A0~A7 位(Column Address) */
NF_ADDR(blockPage&0xff); /* A9-A16, (Page Address) */
NF_ADDR((blockPage>>8)&0xff); /* A17-A24, (Page Address) */
NF_ADDR((blockPage>>16)&0xff); /* A25, (Page Address) */
/* 写OOB 数据块 */
for(i=0;i<16;i++)
{
NF_WRDATA(seBuf[i]);
}
NF_CMD(0x10); /* 结束写命令 */
/* 等待NandFlash 准备好 */
for(i=0;i<10;i++); /* tWB = 100ns. */
NF_WAITRB();
/*读NandFlash 的写状态 */
NF_CMD(0x70);
for(i=0;i<3;i++); /* twhr=60ns */
if (NF_RDDATA()&0x1)
{
NF_nFCE_H(); /* 取消Nand Flash 选中*/
return 0;
} else {
NF_nFCE_H(); /* 取消Nand Flash 选中*/
}
return 1;
}
2.3.7 Nand Flash检查坏块
检查指定块是否是坏块.
参数说明:block:块号
返回值:1:指定块是坏块
0:指定块不是坏块。
static int NF_IsBadBlock(U32 block)
{
int i;
unsigned int blockPage;
U8 data;
blockPage=(block<<5);
NF_nFCE_L(); /* 片选Nand Flash 芯片*/
NF_CMD(0x50); /* Read OOB 数据块 */
NF_ADDR(517&0xf); /* A0~A7 位(Column Address) */
NF_ADDR(blockPage&0xff); /* A9-A16, (Page Address) */
NF_ADDR((blockPage>>8)&0xff); /* A17-A24, (Page Address) */
NF_ADDR((blockPage>>16)&0xff); /* A25, (Page Address) */
/* 等待NandFlash 准备好 */
for(i=0;i<10;i++); /* wait tWB(100ns) */
NF_WAITRB();
/* 读取读出值 */
data=NF_RDDATA();
NF_nFCE_H(); /* 取消Nand Flash 选中*/
/* 如果data 不为0xff 时, 表示该块是坏块 */
if(data != 0xff)
return 1;
else
return 0;
}
2.3.8 擦除指定块中数据
参数说明:block 块号
返回值:0:擦除错误。(若是坏块直接返回0;若擦除出现错误则标记为坏块然后返回0)
1 :成功擦除。
static int NF_EraseBlock(unsigned int block)
{
unsigned int blockPage=(block<<5);
int i;
/* 如果该块是坏块, 则返回 */
if(NF_IsBadBlock(block))
return 0;
NF_nFCE_L(); /* 片选Nand Flash 芯片*/
NF_CMD(0x60); /* 设置擦写模式 */
NF_ADDR(blockPage&0xff); /* A9-A16, (Page Address) , 是基于块擦*/
NF_ADDR((blockPage>>8)&0xff); /* A17-A24, (Page Address) */
NF_ADDR((blockPage>>16)&0xff); /* A25, (Page Address) */
NF_CMD(0xd0); /* 发送擦写命令, 开始擦写 */
/* 等待NandFlash 准备好 */
for(i=0;i<10;i++); /* tWB(100ns) */
NF_WAITRB();
/* 读取操作状态 */
NF_CMD(0x70);
if (NF_RDDATA()&0x1)
{
NF_nFCE_H(); /* 取消Nand Flash 选中*/
NF_MarkBadBlock(block); /* 标记为坏块 */
return 0;
} else {
NF_nFCE_H(); /* 取消Nand Flash 选中*/
return 1;
}
}
2.4 第一层的实现
2.4.1 NandFlash 烧写主函数说明
参数说明: block 块号
srcAddress SDRAM 中数据起始地址
fileSize 要烧写的数据长度
返回值: 无
void K9S1208_Program(unsigned int block, unsigned int srcAddress, unsigned int fileSize)
{
int i;
int programError=0;
U32 blockIndex;
U8 *srcPt, *saveSrcPt;
srcPt=(U8 *)srcAddress; /* 文件起始地址 */
blockIndex = block; /* 块号 */
while(1)
{
saveSrcPt=srcPt;
/* 如果当前块是坏块, 跳过当前块 */
if(NF_IsBadBlock(blockIndex))
{
blockIndex++; /* 到下一个块 */
continue;
}
/* 在写之前, 必须先擦除, 如果擦除不成功, 跳过当前块 */
if(!NF_EraseBlock(blockIndex))
{
blockIndex++; /* 到下一个块 */
continue;
}
/* 写一个块, 一块有32 页 */
for(i=0;i<32;i++)
{
/* 写入一个页, 如果出错, 停止写当前块 */
if(!NF_WritePage(blockIndex,i,srcPt))
{
programError=1;
break;
}
/* 如果操作正常, 文件的写位置加上1 页偏移,到下一页的起始位置 */
srcPt+=512;
/* 如果写地址没有超过文件长度, 继续; 超出则终止写 */
if((U32)srcPt>=(srcAddress+fileSize))
break;
}
/* 如果写一个块时, 其中某一页写失败, 则把写地址恢复写该块之前, 并跳过当前块 */
if(programError==1)
{
blockIndex++;
srcPt=saveSrcPt;
programError=0;
continue;
}
/* 如果写地址没有超过文件长度, 继续; 超出则终止写 */
if((U32)srcPt >= (srcAddress+fileSize))
break;
/* 如果正常写成功, 继续写下一个块 */
blockIndex++;
}
}
3 在 U-BOOT对Nand Flash 的支持
3.1 U-BOOT 对从Nand Flash启动的支持
3.1.1 从Nand Flash启动 U-BOOT的基本原理
1. 前4K 的问题
如果S3C2410 被配置成从Nand Flash 启动(配置由硬件工程师在电路板设置), S3C2410 的Nand Flash 控制器
有一个特殊的功能, 在S3C2410 上电后, Nand Flash 控制器会自动的把Nand Flash 上的前4K 数据搬移到4K 内部
RAM 中, 并把0x00000000 设置内部RAM 的起始地址, CPU 从内部RAM 的0x00000000 位置开始启动。这个过
程不需要程序干涉。
程序员需要完成的工作,是把最核心的启动程序放在Nand Flash 的前4K 中。
2. 启动程序的安排
由于Nand Flash 控制器从Nand Flash 中搬移到内部RAM 的代码是有限的,所以, 在启动代码的前4K 里,我
们必须完成S3C2410 的核心配置以及把启动代码(UBOOT)剩余部分搬到RAM 中运 。以UBOOT 为例, 前4K
完成的主要工作, 见第四部分的2.2 节。
3.1.2 支持 Nand Flash启动代码说明
首先在include/configs/crane2410.h 中加入CONFIG_S3C2410_NAND_BOOT, 如下:
#define CONFIG_S3C2410_NAND_BOOT 1
支持从Nand Flash 中启动.
1. 执 Nand Flash 初始化
下面代码在cpu/arm920t/start.S 中
#ifdef CONFIG_S3C2410_NAND_BOOT
copy_myself:
mov r10, lr
ldr sp, DW_STACK_START @安装栈的起始地址
mov fp, #0 @初始化帧指针寄存器
bl nand_reset @跳到复位C 函数去执
...
DW_STACK_START:
.word STACK_BASE+STACK_SIZE-4
2. nand_reset C 代码
下面代码被加在/board/crane2410/crane2410.c 中
void nand_reset(void)
{
int i;
/* 设置Nand Flash 控制器 */
rNFCONF=(1<<15)|(1<<14)|(1<<13)|(1<<12)|(1<<11)|(TACLS<<8)|(TWRPH0<<4)|(TWRPH1<<0);
/* Nand Flash 芯片发送复位命令 */
NF_nFCE_L();
NF_CMD(0xFF);
for(i=0; i<10; i++);
NF_WAITRB();
NF_nFCE_H();
}
3. 从Nand Flash 中把UBOOT 拷贝到RAM
@read U-BOOT from Nand Flash to RAM
ldr r0, =UBOOT_RAM_BASE @ 设置第 1 个参数: UBOOT 在RAM 中的起始地址
mov r1, #0x0 @ 设置第2 个参数:Nand Flash 的起始地址
mov r2, #0x20000 @ 设置第3 个参数: UBOOT 的长度(128KB)
bl nand_read_whole @ 调用nand_read_whole(), 该函数在board/crane2410/crane2410.c 中
tst r0, #0x0 @ 如果函数的返回值为0,表示执行成功.
beq ok_nand_read @ 执行内存比较
4. 从Nand Flash 中把数据读入到RAM 中
int nand_read_whole(unsigned char *buf, unsigned long start_addr, int size)
{
int i, j;
/* 如果起始地址和长度不是512 字节(1 页)的倍数, 则返回错误代码 */
if ((start_addr & NAND_BLOCK_MASK) || (size & NAND_BLOCK_MASK)) {
return -1;
}
/* 激活Nand Flash */
NF_nFCE_L();
for(i=0; i<10; i++);
i = start_addr;
while(i < start_addr + size) {
/* 读A 区 */
rNFCMD = 0;
/* 写入读取地址 */
rNFADDR = i & 0xff;
rNFADDR = (i >> 9) & 0xff;
rNFADDR = (i >> 17) & 0xff;
rNFADDR = (i >> 25) & 0xff;
NF_WAITRB();
/* 读出一页(512 字节) */
for(j=0; j < NAND_SECTOR_SIZE; j++, i++) {
*buf = (rNFDATA & 0xff);
buf++;
}
}
/* 停止驱动Nand Flash */
NF_nFCE_H();
return 0;
}
5. 校查搬移后的数据
把RAM 中的前4K 与内部中前4K 进 比较, 如果完全相同, 则表示搬移成功.
ok_nand_read:
mov r0, #0x00000000 @内部RAM 的起始地址
ldr r1, =UBOOT_RAM_BASE @UBOOT 在RAM 中的起始地址
mov r2, #0x400 @比较1024 次, 每次4 字节, 4 bytes * 1024 = 4K-bytes
go_next: @ 比较1024 次, 每次4 个字节
ldr r3, [r0], #4
ldr r4, [r1], #4
teq r3, r4
bne notmatch
subs r2, r2, #4
beq done_nand_read
bne go_next
notmatch:
1:b 1b
done_nand_read:
mov pc, r10
3.2 U-BOOT 对Nand Flash 命令的支持
在U-BOOT 下对Nand Flash 的支持主要是在命令行下实现对nand flash 的操作。对nand flash 实现的命令
为:nand info 、nand device 、nand read 、nand write 、nand erease 、nand bad 。
用到的主要数据结构有:struct nand_flash_dev 、struct nand_chip 。前者包括主要的芯片型号、存储容量、
设备ID 、I/O 总线宽度等信息;后者是具体对nand flash 进行操作时用到的信息。
3.2.1 主要数据结构介绍
1. struct nand_flash_dev 数据结构
该数据结构在include/linux/mtd/nand.h 中定义,在include/linux/mtd/nand_ids.h 中赋初值。
struct nand_flash_dev {
char *name; /* 芯片名称 */
int manufacture_id; /* 厂商ID */
int model_id; /* 模式ID */
int chipshift; /* Nand Flash 地址位数 */
char page256; /* 表明是否时256 字节一页。1:是;0:否。*/
char pageadrlen; /* 完成一次地址传送需要 NFADDR 中传送几次。*/
unsigned long erasesize; /* 一次块擦除可以擦除多少字节 */
int bus16; /* 地址线是否是16位,1:是;0:否 */
};
2. struct nand_chip 数据结构
该数据结构在include/linux/mtd/nand.h 中定义. 该结构体定义出一个Nand Flash 设备数组:
struct nand_chip nand_dev_desc[CFG_MAX_NAND_DEVICE];
该数组在nand_probe()中对其进行初始化.
struct nand_chip {
int page_shift; /* Page 地址位数 */
u_char *data_buf; /* 本次读出的一页数据 */
u_char *data_cache; /* 读出的一页数据 */
int cache_page; /* 上次操作的页号 */
u_char ecc_code_buf[6]; /* ECC 校验码 */
u_char reserved[2];
char ChipID; /* 芯片ID 号 */
struct Nand *chips; /* Nand Flash 芯片列表, 表示支持几个芯片为一个设备*/
int chipshift;
char* chips_name; /* Nand Flash 芯片名称 */
unsigned long erasesize; /* 块擦写的大小 */
unsigned long mfr; /* 厂商ID */
unsigned long id; /* 模式ID */
char* name; /* 设备名称 */
int numchips; /* 有几块Nand Flash 芯片 */
char page256; /* 一页是256 字节, 还是512 字节 */
char pageadrlen; /* 页地址的长度 */
unsigned long IO_ADDR; /* 用于对nand flash 进行寻址的地址值存放处 */
unsigned long totlen; /* Nand Flash 总共大小 */
uint oobblock; /* 一页的大小。本款nand flash 为512 */
uint oobsize; /* spare array 大小。本款nand flash 为16 */
uint eccsize; /* ECC 大小 */
int bus16; /* 地址线是否是16位,1:是;0:否 */
};
3.2.2 支持的命令函数说明
1. nand info/nand device
功能:显示当前nand flash 芯片信息。
函数调用关系如下(按先后顺序):
static void nand_print(struct nand_chip *nand) ;
2. nand erase
功能:擦除指定块上的数据。
函数调用关系如下(按先后顺序):
int nand_erase(struct nand_chip* nand, size_t ofs, size_t len, int clean);
3. nand bad
功能:显示坏块。
函数调用关系如下(按先后顺序):
static void nand_print_bad(struct nand_chip* nand);
int check_block (struct nand_chip *nand, unsigned long pos);
4. nand read
功能:读取nand flash 信息到SDRAM 。
函数调用关系如下(按先后顺序):
int nand_rw (struct nand_chip* nand, int cmd,size_t start, size_t len, size_t * retlen, u_char * buf);
static int nand_read_ecc(struct nand_chip *nand, size_t start, size_t len,
size_t * retlen, u_char *buf, u_char *ecc_code);
static void NanD_ReadBuf (struct nand_chip *nand, u_char * data_buf, int cntr);
READ_NAND(adr);
5. nand write
功能:从SDRAM 写数据到nand flash 中。
函数调用关系如下(按先后顺序):
int nand_rw (struct nand_chip* nand, int cmd,size_t start, size_t len, size_t * retlen, u_char * buf);
static int nand_write_ecc (struct nand_chip* nand, size_t to, size_t len,
size_t * retlen, const u_char * buf, u_char * ecc_code);
static int nand_write_page (struct nand_chip *nand, int page, int col, int last, u_char * ecc_code);
WRITE_NAND(d , adr);
3.2.3 U-BOOT支持 Nand Flash命令移植说明
1. 设置配置选项
在CONFIG_COMMANDS 中, 打开CFG_CMD_NAND 选项.
#define CONFIG_COMMANDS /
(CONFIG_CMD_DFL | /
CFG_CMD_CACHE | /
CFG_CMD_NAND | /
/*CFG_CMD_EEPROM |*/ /
/*CFG_CMD_I2C |*/ /
/*CFG_CMD_USB |*/ /
CFG_CMD_PING | /
CFG_CMD_REGINFO | /
CFG_CMD_DATE | /
CFG_CMD_ELF)
#if (CONFIG_COMMANDS & CFG_CMD_NAND)
#define CFG_NAND_BASE 0x4E000000 /* Nand Flash 控制器在SFR 区中起始寄存器地址 */
#define CFG_MAX_NAND_DEVICE 1 /* 支持的最在Nand Flash 数据 */
#define SECTORSIZE 512 /* 1 页的大小 */
#define NAND_SECTOR_SIZE SECTORSIZE
#define NAND_BLOCK_MASK (NAND_SECTOR_SIZE – 1) /* 页掩码 */
#define ADDR_COLUMN 1 /* 一个字节的Column 地址 */
#define ADDR_PAGE 3 /* 3 字节的页块地址, A9-A25*/
#define ADDR_COLUMN_PAGE 4 /* 总共4 字节的页块地址 */
#define NAND_ChipID_UNKNOWN 0x00 /* 未知芯片的ID 号 */
#define NAND_MAX_FLOORS 1
#define NAND_MAX_CHIPS 1
/* Nand Flash 命令层底层接口函数 */
#define WRITE_NAND_COMMAND(d, adr) do {rNFCMD = d; } while(0)
#define WRITE_NAND_ADDRESS(d, adr) do {rNFADDR = d;} while(0)
#define WRITE_NAND(d, adr) do {rNFDATA = d;} while(0)
#define READ_NAND(adr) (rNFDATA)
#define NAND_WAIT_READY(nand) {while(!(rNFSTAT&(1<<0)));}
#define NAND_DISABLE_CE(nand) {rNFCONF |= (1<<11);}
#define NAND_ENABLE_CE(nand) {rNFCONF &= ~(1<<11);}
/* 下面一组操作对Nand Flash 无效 */
#define NAND_CTL_CLRALE(nandptr)
#define NAND_CTL_SETALE(nandptr)
#define NAND_CTL_CLRCLE(nandptr)
#define NAND_CTL_SETCLE(nandptr)
/* 允许Nand Flash 写校验 */
#define CONFIG_MTD_NAND_VERIFY_WRITE 1
#endif /* CONFIG_COMMANDS & CFG_CMD_NAND*/
2. 加入自己的Nand Flash 芯片型号
在include/linux/mtd/ nand_ids.h 中的对如下结构体赋值进行修改:
static struct nand_flash_dev nand_flash_ids[] = {
......
{"Samsung K9F1208U0B", NAND_MFR_SAMSUNG, 0x76, 26, 0, 4, 0x4000, 0},
.......
}
这样对于该款Nand Flash 芯片的操作才能正确执 。
3. 编写自己的Nand Flash 初始化函数
在board/crane2410/crane2410.c 中加入nand_init()函数.
void nand_init(void)
{
/* 初始化Nand Flash 控制器, 以及Nand Flash 芯片 */
nand_reset();
/* 调用nand_probe()来检测芯片类型 */
printf ("%4lu MB/n", nand_probe(CFG_NAND_BASE) >> 20);
}
该函数在启动时被start_armboot()调用.
4 在 Linux对Nand Flash 的支持
4.1 Linux 下Nand Flash 调用关系
4.1.1 Nand Flash设备添加时数据结构包含关系
struct mtd_partition partition_info[]
--> struct s3c2410_nand_set nandset
--> struct s3c2410_platform_nand superlpplatfrom
--> struct platform_device s3c_device_nand
在该数据结构的name字段的初始化值"s3c2410-nand",必须与Nand Flash设备驱动注册时
struct device_driver结构中的name字段相同,因为platfrom bus是依靠名字来匹配的.
--> struct platform_device *smdk2410_devices[]
4.1.2 Nand Flash设备注册时数据结构包含关系
struct device_driver s3c2410_nand_driver
-->struct device *dev
该数据构由系统分配.
-->struct platform_device *pdev
-->struct s3c2410_platform_nand *plat
-->struct s3c2410_nand_set nset
-->struct mtd_partition
4.1.3 当发生系统调用时数据结构调用关系
struct mtd_info
它的*priv指向chip
-->struct nand_chip
它的*priv指向nmtd
-->struct s3c2410_nand_mtd
它是s3c2410_nand_info 的一个字段
-->s3c2410_nand_info
它被设为Nand Flash设备驱动的私有数据结构,在Nand Flash设备驱动注册时分配空间.
-->struct device
4.2 Linux 下Nand Flash 驱动主要数据结构说明
4.2.1 s3c2410专有数据结构
1. s3c2410_nand_set
struct s3c2410_nand_set {
int nr_chips; /* 芯片的数目 */
int nr_partitions; /* 分区的数目 */
char *name; /* 集合名称 */
int nr_map; /* 可选, 底层逻辑到物理的芯片数目 */
struct mtd_partition partitions; /* 分区列表 */
};
2. s3c2410_platform_and
struct s3c2410_platform_nand {
/* timing information for controller, all times in nanoseconds */
int tacls; /* 从CLE/ALE有效到 nWE/nOE 的时间 */
int twrph0; /* nWE/nOE 的有效时间 */
int twrph1; /* 从释放CLE/ALE 到nWE/nOE不活动的时间 */
int nr_sets; /* 集合数目 */
struct s3c2410_nand_set sets; /* 集合列表 */
/* 根据芯片编号选择有效集合 */
void (*select_chip)(struct s3c2410_nand_set , int chip);
};
3. s3c2410_nand_mtd
在drivers/mtd/nand/s3c2410.c 中,
struct s3c2410_nand_mtd {
struct mtd_info mtd; /* MTD 信息 */
struct nand_chip chip; /* nand flash 芯片信息 */
struct s3c2410_nand_set set; /* nand flash 集合 */
struct s3c2410_nand_info *info; /* nand flash 信息 */
int scan_res;
};
4. s3c2410_nand_info
struct s3c2410_nand_info {
/* mtd info */
struct nand_hw_control controller; /* 硬件控制器 */
struct s3c2410_nand_mtd *mtds; /* MTD 设备表 */
struct s3c2410_platform_nand platform; /* Nand 设备的平台 */
/* device info */
struct device *device; /* 设备指针 */
struct resource *area; /* 资源指针 */
struct clk *clk; /* Nand Flash 时钟 */
void __iomem *regs; /* 寄存器基地址(map后的逻辑地址) */
int mtd_count; /* MTD的数目 */
unsigned char is_s3c2440;
};
5. struct clk
在arch/arm/mach-s3c2410/clock.h 中
struct clk {
struct list_head list; /* clock 列表结点 */
struct module *owner; /* 所属模块 */
struct clk *parent; /* 父结点 */
const char *name; /* 名称 */
int id; /* 编号 */
atomic_t used; /* 使用者计数 */
unsigned long rate; /* 时钟速率 */
unsigned long ctrlbit; /* 控制位 */
int (*enable)(struct clk *, int enable); /* Clock打开方法 */
};
4.2.2 Linux 通用数据结构说明
1. device_driver
include/linux/device.h
struct device_driver {
const char * name; /* 驱动名称 */
struct bus_type * bus; /* 总线类型 */
struct completion unloaded; /* 卸载事件通知 制 */
struct kobject kobj; /* sys中的对象 */
struct klist klist_devices; /* 设备列表 */
struct klist_node knode_bus; /* 总线结点列表 */
struct module * owner;/* 所有者 */
/* 设备驱动通用方法 */
int (*probe) (struct device * dev); /* 探测设备 */
int (*remove) (struct device * dev); /* 移除设备 */
void (*shutdown) (struct device * dev); /* 关闭设备 */
/* 挂起设备 */
int (*suspend) (struct device * dev, pm_message_t state, u32 level);
int (*resume) (struct device * dev, u32 level); /* 恢复 */
};
2. platform_device
include/linux/device.h
struct platform_device {
const char * name; /* 名称 */
u32 id; /* 设备编号, -1表示不支持同类多个设备 */
struct device dev; /* 设备 */
u32 num_resources; /* 资源数 */
struct resource * resource; /* 资源列表 */
};
3. resource
struct resource {
const char name; /* 资源名称 */
unsigned long start, end; /* 开始位置和结束位置 */
unsigned long flags; /* 资源类型 */
/* 资源在资源树中的父亲,兄弟和孩子 */
struct resource *parent, *sibling, *child;
};
4. device
include/linux/device.h
struct device {
struct klist klist_children; /* 在设备列表中的孩子列表 */
struct klist_node knode_parent; /* 兄弟结点 */
struct klist_node knode_driver; /* 驱动结点 */
struct klist_node knode_bus; /* 总线结点 */
struct device parent; /* 父亲 */
struct kobject kobj; /* sys结点 */
char bus_id[BUS_ID_SIZE];
struct semaphore sem; /* 同步驱动的信号量 */
struct bus_type * bus; /* 总线类型 */
struct device_driver *driver; /* 设备驱动 */
void *driver_data; /* 驱动的私有数据 */
void *platform_data; /* 平台指定的数据,为device核心驱动保留 */
void *firmware_data; /* 固件指定的数据,为device核心驱动保留 */
struct dev_pm_info power; /* 设备电源管理信息 */
u64 *dma_mask; /* DMA掩码 */
u64 coherent_dma_mask;
struct list_head dma_pools; /* DMA缓冲池 */
struct dma_coherent_mem *dma_mem; /* 连续DMA 内存的起始位置 */
void (*release)(struct device * dev); /* 释放设置方法 */
};
5. nand_hw_control
include/linux/mtd/nand.h
struct nand_hw_control {
spinlock_t lock; /* 自旋锁,用于硬件控制 */
struct nand_chip *active; /* 正在处理MTD设备 */
wait_queue_head_t wq; /* 等待队列 */
};
6. nand_chip
include/linux/mtd/nand.h
struct nand_chip {
void __iomem *IO_ADDR_R; /* 读地址 */
void __iomem *IO_ADDR_W; /* 写地址 */
/* 字节操作 */
u_char (*read_byte)(struct mtd_info *mtd); /* 读一个字节 */
void (*write_byte)(struct mtd_info *mtd, u_char byte); /* 写一个字节 */
/* 双字节操作 */
u16 (*read_word)(struct mtd_info mtd); /* 读一个字 */
void (*write_word)(struct mtd_info *mtd, u16 word); /* 写一个字 */
/* buffer操作 */
void (*write_buf)(struct mtd_info *mtd, const u_char *buf, int len);
void (*read_buf)(struct mtd_info *mtd, u_char *buf, int len);
int (*verify_buf)(struct mtd_info *mtd, const u_char *buf, int len);
/* 选择一个操作芯片 */
void (*select_chip)(struct mtd_info *mtd, int chip);
/* 坏块检查操作 */
int (*block_bad)(struct mtd_info *mtd, loff_t ofs, int getchip);
/* 坏块标记操作 */
int (*block_markbad)(struct mtd_info *mtd, loff_t ofs);
/* 硬件控制操作 */
void (*hwcontrol)(struct mtd_info *mtd, int cmd);
/* 设备准备操作 */
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, int state);
/* 计算ECC码操作 */
int (*calculate_ecc)(struct mtd_info *mtd, const u_char *dat, u_char
*ecc_code);
/* 数据纠错操作 */
int (*correct_data)(struct mtd_info *mtd, u_char *dat, u_char *read_ecc,
u_char *calc_ecc);
/* 开启硬件ECC */
void (*enable_hwecc)(struct mtd_info *mtd, int mode);
/* 擦除操作 */
void (*erase_cmd)(struct mtd_info *mtd, int page);
/* 检查坏块表 */
int (*scan_bbt)(struct mtd_info *mtd);
int eccmode; /* ECC模式 */
int eccsize; /* ECC 计算时使用的字节数 */
int eccbytes; /* ECC 码的字节数 */
int eccsteps; /* ECC 码计算的步骤数 */
int chip_delay; /* 芯片的延迟时间 */
spinlock_t chip_lock; /* 芯片访问的自旋锁 */
wait_queue_head_t wq; /* 芯片访问的等待队列 */
nand_state_t state; /* Nand Flash状态 */
int page_shift; /* 页右移的位数,即column地址位数 */
int phys_erase_shift; /* 块右移的位数, 即column和页一共的地址位数 */
int bbt_erase_shift; /* 坏块页表的位数 */
int chip_shift; /* 该芯片总共的地址位数 */
u_char *data_buf; /* 数据缓冲区 */
u_char *oob_buf; /* oob 缓冲区 */
int oobdirty; /* oob 缓冲区是否需要重新初始化 */
u_char *data_poi; /* 数据缓冲区指针 */
unsigned int options; /* 芯片专有选项 */
int badblockpos;/* 坏块标示字节在OOB 中的位置 */
int numchips; /* 芯片的个数 */
unsigned long chipsize; /* 在多个芯片组中, 一个芯片的大小 */
int pagemask; /* 每个芯片页数的屏蔽字, 通过它取出每个芯片包含多少个页 */
int pagebuf; /* 在页缓冲区中的页号 */
struct nand_oobinfo *autooob; /* oob信息 */
uint8_t *bbt; /* 坏块页表 */
struct nand_bbt_descr *bbt_td; /* 坏块表描述 */
struct nand_bbt_descr *bbt_md; /* 坏块表镜像描述 */
struct nand_bbt_descr *badblock_pattern; /* 坏块检测模板 */
struct nand_hw_control *controller; /* 硬件控制 */
void *priv; /* 私有数据结构 */
/* 进行附加错误检查 */
int (*errstat)(struct mtd_info *mtd, struct nand_chip *this, int state, int
status, int page);
};
7. mtd_info
include/linux/mtd/mtd.h
struct mtd_info {
u_char type; /* 设备类型 */
u_int32_t flags; /* 设备标志位组 */
u_int32_t size; /* 总共设备的大小 */
u_int32_t erasesize; /* 擦除块的大小 */
u_int32_t oobblock; /* OOB块的大小,如:512个字节有一个OOB */
u_int32_t oobsize; /* OOB数据的大小,如:一个OOB块有16个字节 */
u_int32_t ecctype; /* ECC校验的类型 */
u_int32_t eccsize; /* ECC码的大小 */
char *name; /* 设备名称 */
int index; /* 设备编号 */
/* oobinfo信息,它可以通过 MEMSETOOBINFO ioctl命令来设置 */
struct nand_oobinfo oobinfo;
u_int32_t oobavail; /* OOB区的有效字节数,为文件系统提供 */
/* 数据擦除边界信息 */
int numeraseregions;
struct mtd_erase_region_info *eraseregions;
u_int32_t bank_size; /* 保留 */
/* 擦除操作 */
int (*erase) (struct mtd_info *mtd, struct erase_info *instr);
/* 指向某个执行代码位置 */
int (*point) (struct mtd_info *mtd, loff_t from,
size_t len, size_t *retlen, u_char **mtdbuf);
/* 取消指向 */
void (*unpoint) (struct mtd_info *mtd, u_char * addr, loff_t from, size_t len);
/* 读/写操作 */
int (*read) (struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char *buf);
int (*write) (struct mtd_info *mtd, loff_t to, size_t len,
size_t *retlen, const u_char *buf);
/* 带ECC码的读/写操作 */
int (*read_ecc) (struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen,
u_char *buf, u_char *eccbuf, struct nand_oobinfo *oobsel);
int (*write_ecc) (struct mtd_info *mtd, loff_t to, size_t len, size_t *retlen,
const u_char *buf, u_char *eccbuf, struct nand_oobinfo *oobsel);
/* 带OOB码的读/写操作 */
int (*read_oob) (struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen,
u_char *buf);
int (*write_oob) (struct mtd_info *mtd, loff_t to, size_t len, size_t *retlen,
const u_char *buf);
/* 提供访问保护寄存器区的方法 */
int (*get_fact_prot_info) (struct mtd_info *mtd, struct otp_info *buf, size_t len);
int (*read_fact_prot_reg) (struct mtd_info *mtd, loff_t from, size_t len,
size_t *retlen, u_char *buf);
int (*get_user_prot_info) (struct mtd_info *mtd, struct otp_info *buf, size_t len);
int (*read_user_prot_reg) (struct mtd_info *mtd, loff_t from, size_t len,
size_t *retlen, u_char *buf);
int (*write_user_prot_reg) (struct mtd_info *mtd, loff_t from, size_t len,
size_t *retlen, u_char *buf);
int (*lock_user_prot_reg) (struct mtd_info *mtd, loff_t from, size_t len);
/* 提供readv和writev方法 */
int (*readv) (struct mtd_info *mtd, struct kvec *vecs, unsigned long count,
loff_t from, size_t *retlen);
int (*readv_ecc) (struct mtd_info *mtd, struct kvec *vecs, unsigned long count,
loff_t from, size_t *retlen, u_char *eccbuf,
struct nand_oobinfo *oobsel);
int (*writev) (struct mtd_info *mtd, const struct kvec *vecs,
unsigned long count, loff_t to, size_t *retlen);
int (*writev_ecc) (struct mtd_info *mtd, const struct kvec *vecs,
unsigned long count, loff_t to, size_t *retlen,
u_char *eccbuf, struct nand_oobinfo *oobsel);
/* 同步操作 */
void (*sync) (struct mtd_info *mtd);
/* 芯片级支持的加/解锁操作 */
int (*lock) (struct mtd_info *mtd, loff_t ofs, size_t len);
int (*unlock) (struct mtd_info *mtd, loff_t ofs, size_t len);
/* 电源管理操作 */
int (*suspend) (struct mtd_info *mtd);
void (*resume) (struct mtd_info *mtd);
/* 坏块管理操作 */
int (*block_isbad) (struct mtd_info *mtd, loff_t ofs);
int (*block_markbad) (struct mtd_info *mtd, loff_t ofs);
/* 重启前的通知事件 */
struct notifier_block reboot_notifier;
void *priv; /* 私有数据结构 */
struct module *owner; /* 模块所有者 */
int usecount; /* 使用次数 */
};
4.3 Linux 下Nand Flash 驱动说明
4.3.1 注册driver_register
通过module_init(s3c2410_nand_init);注册Nand Flash 驱动. 在 s3c2410_nand_init ()中通过driver_register()注册
s3c2410_nand_driver驱动程序,如下所示:
static struct device_driver s3c2410_nand_driver = {
.name = "s3c2410-nand",
.bus = &platform_bus_type, /* 在drivers/base/platform.c 中定义 */
.probe = s3c2410_nand_probe,
.remove = s3c2410_nand_remove,
};
4.3.2 探测设备probe
在注册的Nand Flash驱动程序中, probe 方法为s3c2410_nand_probe(). s3c2410_nand_probe()再调用
s3c24xx_nand_probe(). 在该函数中, 把*info 作为Nand Flash 驱动的私有数据结构, 并通过dev_set_drvdata(dev,
info)把*info 保存在*device 的*driver_data 字段中.然后通过clk_get(dev, "nand")获取Nand Flash的时钟资
源, clk_use(info->clk)增加时钟资源的使用计数, clk_enable(info->clk)开启资源.填写*info 的其它字段,
其中包括:
1. 通过request_mem_region()为Nand Flash 寄存器区申请I/O 内存地址空间区,并通过ioremap()把它映射到虚
拟地址空间.
2. 调用s3c2410_nand_inithw()初始化Nand Flash 控制器.
3. 为mtd 设备分配设备信息的存储空间.
4. 对当前mtd 设备,调用s3c2410_nand_init_chip()进行初始化.
5. 对当前mtd 设备, 调用nand_scan()检测Nand Flash 芯片, nand_scan()函数在drivers/mtd/nand/nand_base.c 中
定义.该函数的作用是初始化struct nand_chip 中一些方法, 并从Nand Flash 中读取芯片ID, 并初始化struct
mtd_info 中的方法.
6. 对当前mtd 设备,加入其分区信息.
7. 如果还有更多mtd 设备,到4 执 .
4.3.3 初始化 Nand Flash控制器
s3c2410_nand_inithw()函数会初始化Nand Flash 控制器, 通过设置Nand Flash 控制寄存器(S3C2410_NFCONF)来
完成, 这里最重要的是根据S3C2410 的PCLK 计算出tacls, twrph0 以及twrph1 值.
4.3.4 移除设备
s3c2410_nand_remove()当设备被移除时,被device 核心驱动调用.它完成的主要工作如下:
1. 把*device 的*driver_data 字段置空.
2. 释放mtd 设备信息.
3. 释放clk 资源.
4. 通过iounmap()取消映地址空间.
5. 释放申请的I/O 内存资源.
6. 释放设备私有数据*info 的空间.
4.3.5 Nand Flash芯片初始化
s3c2410_nand_init_chip()初始化struct nand_chip 中的一些主要字段以及方法.其中主要包括的方法有:
1. s3c2410_nand_hwcontrol(); 硬件控制
2. s3c2410_nand_devready(); 设备是否准备好
3. s3c2410_nand_write_buf(); 写一个buffer 到nand flash
4. s3c2410_nand_read_buf(); 读一个buffer 到nand flash
5. s3c2410_nand_select_chip(); 选择操作芯片
如果支持ECC 硬件校验,还设置如下方法:
1. s3c2410_nand_correct_data(); 通过ECC 码校正数据
2. s3c2410_nand_enable_hwecc(); 开启硬件ECC 检查
3. s3c2410_nand_calculate_ecc(); 计算ECC 码
4.3.6 读Nand Flash
当对nand flash 的设备文件(nand flash 在/dev 下对应的文件)执行系统调用read(),或在某个文件系统中对该
设备进 读操作时. 会调用struct mtd_info 中的read方法,他们缺省调用函数为nand_read(),在
drivers/mtd/nand/nand_base.c 中定义.nand_read()调用nand_do_read_ecc(),执行读操作. 在
nand_do_read_ecc()函数中,主要完成如下几项工作:
1. 会调用在nand flash驱动中对struct nand_chip重载的select_chip方法,即
s3c2410_nand_select_chip()选择要操作的MTD 芯片.
2. 会调用在struct nand_chip 中系统缺省的方法cmdfunc发送读命令到nand flash.
3. 会调用在nand flash驱动中对struct nand_chip重载的read_buf(),即s3c2410_nand_read_buf()
从Nand Flash的控制器的数据寄存器中读出数据.
4. 如果有必要的话,会调用在nand flash驱动中对struct nand_chip重载的
enable_hwecc,correct_data 以及calculate_ecc方法,进行数据ECC校验。
4.3.7 写 Nand Flash
当对nand flash 的设备文件(nand flash 在/dev 下对应的文件)执行系统调用write(),或在某个文件系统中对该设备
进 读操作时, 会调用struct mtd_info 中write方法,他们缺省调用函数为nand_write(),这两个函数在
drivers/mtd/nand/nand_base.c 中定义. nand_write()调用nand_write_ecc(),执行写操作.在
nand_do_write_ecc()函数中,主要完成如下几项工作:
1. 会调用在nand flash驱动中对struct nand_chip重载的select_chip方法,即
s3c2410_nand_select_chip()选择要操作的MTD 芯片.
2. 调用nand_write_page()写一个页.
3. 在nand_write_page()中,会调用在struct nand_chip 中系统缺省的方法cmdfunc发送写命令
到nand flash.
4. 在nand_write_page()中,会调用在nand flash驱动中对struct nand_chip重载的
write_buf(),即s3c2410_nand_write_buf()从Nand Flash的控制器的数据寄存器中写入数据.
5. 在nand_write_page()中,会调用在nand flash驱动中对struct nand_chip重载waitfunc方法,
该方法调用系统缺省函数nand_wait(),该方法获取操作状态,并等待nand flash操作完成.等
待操作完成,是调用nand flash驱动中对struct nand_chip 中重载的dev_read 方法,即
s3c2410_nand_devready()函数.
作者介绍:本文由尚观科技老师和同学生(刘勇,孙贺,聂强,聂大鹏 ,牛须乐,孙磊)共同创作