一、NAND原理及硬件操作
C: fopen,fread,fwrite
APP: open,read,write “1.txt”
------------------------------------------ 文件读写
文件系统:vfat,ext2,ext3,yaffs (把文件的读写转换成对扇区的读写)
------------------ll_rw_block-------------- 扇区读写
块设备驱动程序
硬件:硬盘、FLASH
1.1NAND FLASH硬件
问1:原理图上 NAND FLASH 和 S3C2440 之间只有数据线(LDATA0~7),没有看到地址引脚,怎么传输地址(如何将地址信号告诉 NAND)?
答1:在 DATA0~DATA7 上既传输数据,又传输地址。用一个信号 ALE 分辨是“地址”还是“信号”,当 ALE 为高电平时传输的是地址。当 ALE 为低电平是传输的是数据
问2:要操作 NAND FLASH 需要先发出命令,只有 8 条DATA0~7 的数据线 怎么传入命令?
答2:在 DATA0~DATA7 上既传输数据,又传输地址,也传输命令。
当 ALE 为高电平时传输的是地址,
当 CLE 为高电平时传输的是命令,
当 ALE 和 CLE 都为低电平时传输的是数据。
问3:数据线既接到 NAND FLASH,也接到 NOR FLASH,还接到 SDRAM、DM9000 等等 那么怎么避免干扰
答3:这些设备,要访问之前必须"选中",没有选中的芯片不会工作,相当于没接一样,要“选中”这就是它们都有“片选” 信号
问4:假设烧写 NAND FLASH,把命令、地址、数据发给它之后 NAND FLASH 肯定不可能瞬间完成烧写的怎么判断烧写完成?
答4:通过状态引脚 RnB 来判断:它为高电平表示就绪,它为低电平表示正忙。
问 5.:怎么操作 NAND FLASH 呢?
答 5.:根据 NAND FLASH 的芯片手册,一般的过程是:
发出命令
发出地址
发出数据 或 读数据
1.2NAND结构
NAND FLASH的结构是一页一页(一个扇区一个扇区)的结构,一页是 2KB,除了这 2KB 外还有 64B 的空间,这个 64B 区叫“OOB”(out of bank 叫作在 BANK 之外的东西)。这个 OOB 区不参与“统一编址”,就是说假若某一页 A 的地址是“2-2047”,那么 2048 这个地址不是在 OOB 区,而是在页 B 上(2048-4095)。
引入 OOB,是因为 NAND 有个缺点:位反转。如读一页数据时,里面很可能有某一位发生了位反转。本来值为 0,读出来为“1”。写的时候也有可能发生“位反转”,这样引入了“ECC”校验
解决位反转:
写时:
1,写一页数据。
2,用这一页数据生成 ECC 码(校验码)。
3,把 ECC 写入 OOB 里。
读时:
1,读整页的数据。
2,读 OOB 里的 ECC 码。3,通过读出来的一页数据算校验码。
4,比较从 OOB 里读出来的 ECC 码和通过读到的一页数据算出来的 ECC 码是否相同不同则是发生了位反转。ECC 码是特定设置的,可以通过它知道是哪一位发生了反转。
ECC 校验码,可以用硬件生成,也可以用软件生成。
二、NAND驱动编写
2.1init函数框架
static int __init s3c_nand_init(void)
{
printk("s3c_nand_init NAND Driver, (c) 2020 Simtec Electronics\n");
/* 1. 分配一个nand_chip结构体 */
s3c_nand = kzalloc(sizeof(struct nand_chip),GFP_KERNEL);
/* 2. 设置nand_chip */
/* 设置nand_chip是给nand_scan函数使用的, 如果不知道怎么设置, 先看nand_scan怎么使用
* 它应该提供:选中,发命令,发地址,发数据,读数据,判断状态的功能
*/
s3c_nand->select_chip = s3c2440_select_chip;
s3c_nand->cmd_ctrl = s3c2440_nand_cmd_ctrl;
s3c_nand->IO_ADDR_R = "NFDATA的虚拟地址";
s3c_nand->IO_ADDR_W = "NFDATA的虚拟地址";
s3c_nand->dev_ready = s3c2440_dev_ready;
/* 3. 硬件相关的设置 */
/* 4. 使用: nand_scan */
s3c_mtd = kzalloc(sizeof(struct mtd_info),GFP_KERNEL);
mtd->priv = nand_chip;
mtd->owner = THIS_MODULE;
nand_scan(s3c_mtd,1); /* 识别NAND FLASH, 构造mtd_info */
/* 5. add_mtd_partitions */
return 0;
}
2.1.1设置nand_chip结构
通过分析"nand_scan"中使用 nand_chip的情况分析如何使用他,如下是nand_scan函数:
nand_scan // drivers/mtd/nand/nand_base.c,构造mtd_info
nand_scan_ident
busw = chip->options & NAND_BUSWIDTH_16; //总线宽度即FLASH为8位还是16位
nand_set_defaults //成员函数为空时默认函数,nandflash协议层
if (!chip->select_chip)
chip->select_chip = nand_select_chip; // 默认值不适用
if (chip->cmdfunc == NULL)
chip->cmdfunc = nand_command;
chip->cmd_ctrl(mtd, command, ctrl);
if (!chip->read_byte)
chip->read_byte = nand_read_byte;
readb(chip->IO_ADDR_R);
if (chip->waitfunc == NULL)
chip->waitfunc = nand_wait;
chip->dev_ready
nand_get_flash_type //硬件相关层
chip->select_chip(mtd, 0);
chip->cmdfunc(mtd, NAND_CMD_READID, 0x00, -1);
*maf_id = chip->read_byte(mtd); //读厂家ID
dev_id = chip->read_byte(mtd); //读设备ID
nand_scan_tail
//ECC
mtd->erase = nand_erase;
mtd->read = nand_read;
mtd->write = nand_write;
片选芯片
chip->select_chip(mtd, 0)平台默认函数如下,并没有做其他操作,因此需要做一些客制化来片选目前的芯片。
static void nand_select_chip(struct mtd_info *mtd, int chipnr)
{
struct nand_chip *chip = mtd->priv;
switch (chipnr) {
case -1:
chip->cmd_ctrl(mtd, NAND_CMD_NONE, 0 | NAND_CTRL_CHANGE);
break;
case 0: //为0时为第一个芯片,里面什么也没做
break;
default:
BUG();
}
}
看2440的NAND控制器可知,片选就是在"NFCONT"寄存器"Reg_nCE"bit1域设置为0,具体如下
static void s3c2440_select_chip(struct mtd_info *mtd, int chipnr)
{
if(chipnr == -1)
{
s3c_nand_regs->nfcont |= (1<<1);
/* 取消选中: NFCONT[1]设为1 */
}else{
s3c_nand_regs->nfcont &= ~(1<<1);
/* 选中: NFCONT[1]设为0 */
}
}
发命令/地址/数据
首先NAND FLASH取地址时,先看哪一页再看是页内的哪个地址
默认函数:chip->cmdfunc = nand_command;
void nand_command(struct mtd_info *mtd, unsigned int command, int column, int page_addr)
//“column”:页内地址(页内哪一个地址)
//“page_addr”:页地址
chip->cmd_ctrl(mtd, readcmd, ctrl);//参 2 为命令值或地址值,参 3 控制了是发命令还是发地址。
查2440 NAND控制器实现2440接口
static void s3c2440_nand_cmd_ctrl(struct mtd_info *mtd, int dat, unsigned int ctrl)
{
if (ctrl & NAND_CLE)
{
/* 发命令: NFCMMD=dat */
s3c_nand_regs->nfcmd = dat;
}else{
/* 发地址: NFADDR=dat */
s3c_nand_regs->nfaddr = dat;
}
}
读数据:厂家ID、设备ID
默认函数:chip->read_byte = busw ? nand_read_byte16 : nand_read_byte
从原理图看,使用的是8bit的nand flash,故使用下面的函数
static uint8_t nand_read_byte(struct mtd_info *mtd)
{
struct nand_chip *chip = mtd->priv;
return readb(chip->IO_ADDR_R);
}
默认函数中需要提供:nand_chip->IO_ADDR_R
s3c_nand->IO_ADDR_R = &s3c_nand_regs->nfdata; //需要提供NFDATA寄存器的地址读数据
写数据
默认函数:chip->write_buf = busw ? nand_write_buf16 : nand_write_buf;
static void nand_write_buf(struct mtd_info *mtd, const uint8_t *buf, int len)
{
int i;
struct nand_chip *chip = mtd->priv;
for (i = 0; i < len; i++)
writeb(buf[i], chip->IO_ADDR_W);
}
判断状态
默认函数:chip->waitfunc = nand_wait
使用2440 nand 控制器中的’NFSTAT"寄存器
static int s3c2440_dev_ready(struct mtd_info *mtd)
{
return (s3c_nand_regs->nfstat & (1<<0));
}
2.1.2设置硬件状态
从上图2440 nand控制器时序图可知
发出“CLE/ALE”后,要过“TACLS”时间才能开始发一个“nWE”写信号。“nWE”写信号变成高电平之后,还要过“TWRPH1”时间,“CLE/ALE”就变成低电平。
再看 NAND 芯片的时序图:
对照数据手册,计算出时间差
#define TACLS 0 //因为TACLS=0
#define TWRPH0 1 //因为TWRPH0 >= 1
#define TWRPH1 0 //因为TWRPH1 >= 0
s3c_nand_regs->nfconf = (TACLS<<12) | (TWRPH0<<8) | (TWRPH1<<4);
为了省电。内核启动时会将一些用不到的模块都关掉。要使用某模块就得使能CLKCON相关位域,开启CLKCON
clk = clk_get(NULL, "nand");
clk_enable(clk); /* CLKCON'bit[4] 设置为1*/
添加分区
若只是把 NAND 分成一个分区,则只要用“add_mtd_device(mtd_info 结构)”就可以。若要构造分区,则用“add_mtd_partitions()
/*
参 1,mtd_info 结构体。
参 2,mtd_partition 结构指针。相当于一个结构数组。最终是数组,组元是结构体。
参 3,就是参 2 这个 mtd_partition 结构数组的组元有多少项。
*/
int add_mtd_partitions
(struct mtd_info *master,const struct mtd_partition *parts,int nbparts)
{
}
static struct mtd_partition s3c_nand_parts[] = {
[0] = {
.name = "bootloader",
.size = 0x00040000,
.offset = 0,
},
[1] = {
.name = "params",
.offset = MTDPART_OFS_APPEND,
.size = 0x00020000,
},
[2] = {
.name = "kernel",
.offset = MTDPART_OFS_APPEND,
.size = 0x00200000,
},
[3] = {
.name = "root",
.offset = MTDPART_OFS_APPEND,
.size = MTDPART_SIZ_FULL,
}
};
2.2exit函数
static void s3c_nand_exit(void)
{
del_mtd_partitions(s3c_mtd);//清除分区结构数组
kfree(s3c_mtd);
iounmap(s3c_nand_regs);
kfree(s3c_nand);
}