20150418 S3C2440 nand_flash驱动程序
2015-04-18 Lover雪儿
一、一个简单的nand_flash驱动
1.定义nand_chip、mtd_info两个结构体
如上图所示:
nand_chip 结构体:是给nand_scan函数用的,而nand_scan函数提供了选中nand、发出命令、发出地址、发出数据、读取数据、判断状态等功能,所以nand_chip结构体上必须定义一系列实现上面功能能的函数,包括选中函数,负责发地址与命令的函数,以及判断状态的函数,最重要的就是io读取的虚拟地址。
mtd_info结构体:MTD(Memory Technology Device)即内存技术设在linux内核中,引入mtd层为NOR Flash和NAND Flash设备提供统一的接口,将文件系统于底层Flash存储设备进行了隔离。
MTD设备可以分为四层,从上到下依次为:设备节点层,MTD设备层,MTD原始设备层,Flash硬件驱动层。
Flash硬件驱动层:负责对Flash硬件的读、写和擦除操作。MTD设备的NAND flash芯片的驱动在drivers/mtd/nand目录下,nor flash芯片驱动位于drivers/mtd/chips目录下。
MTD原始设备层:用于描述MTD原始设备的数据结构体是mtd_info ,它定义了大量的关于MTD的数据和操作函数,其中mtdcore.c:实现原始设备接口的相关实现,mtdpart.c:实现mtd分区接口相关实现。
MTD设备层: 基于MTD原始设备,linux系统可以定义出MTD的块设备(主设备号31)和字符设备(设备号90),其中mtdchar.c实现mtd字符设备接口相关实现,mtdblock.c用于实现块设备接口相关实现。
设备节点层:通过mknode在/dev子目录下建立MTD块设备节点,通过此设备节点即可访问MTD字符设备和块设备。
2.在init函数中初始化结构体
1 static int lhy_nand_init(void){ 2 3 /* 1.分配一个nand_chip结构体 */ 4 lhy_nand = kzalloc(sizeof(struct nand_chip), GFP_KERNEL); 5 /* 2.设置 */ 6 /* 2.1设置nand_chip是给nand_scan函数用的,如果不知道怎么设置,先看nand_scan怎么用 7 * 它应该提供:选中,发命令,发地址,发数据,读数据,判断状态等功能 8 */ 9 lhy_nand->select_chip = lhy_select_chip; //选中,芯片选择函数 10 lhy_nand->cmd_ctrl = lhy_nand_cmd_ctrl; //负责发送地址,命令 11 lhy_nand->IO_ADDR_R = "NFDATA 的虚拟地址"; 12 lhy_nand->IO_ADDR_R = "NFDATA 的虚拟地址"; 13 lhy_nand->dev_ready = lhy_dev_ready; 14 /* 3.硬件相关的操作 */ 15 16 /* 4.使用nand_scan */ 17 lhy_mtd = kzalloc(sizeof(struct mtd_info), GFP_KERNEL); 18 lhy_mtd->priv = lhy_nand; //私有数据为我们的nand_chip结构体 19 lhy_mtd->owner = THIS_MODULE; 20 21 nand_scan(lhy_mtd,1); //扫描识别nand flash,并且构造mtd,最大芯片个数为1 22 /* 5.add_mtd_partitions */ 23 24 25 return 0; 26 }
3.实现上述方法:
1 /* 芯片选择 */ 2 static void lhy_select_chip(struct mtd_info *mtd,int chipnr) 3 { 4 if(chipnr == -1){ 5 /* 取消选中,NFCONT[1]设为0 */ 6 }else{ 7 /* 选中:NFCONT[1]设为1 */ 8 } 9 } 10 //发送命令,地址,数据 11 static void lhy_nand_cmd_ctrl(struct mtd_info *mtd, int cmd, unsigned int ctrl) 12 { 13 if (ctrl & NAND_CLE){ 14 /* 发命令 : NFCMMD=dat*/ 15 writeb(cmd, host->io_base + (1 << host->board->cle)); 16 }else{ 17 writeb(cmd, host->io_base + (1 << host->board->ale)); 18 } 19 } 20 //判断状态 21 static int lhy_dev_ready(struct mtd_info *mtd) 22 { 23 return "NFSTAT 的 bit[0]"; 24 }
附上驱动程序nand_flash1:
1 /* 2 * 参考:linux-2.6.31\drivers\mtd\nand\s3c2410.c atmel_nand.c 3 */ 4 5 #include <linux/slab.h> 6 #include <linux/module.h> 7 #include <linux/moduleparam.h> 8 #include <linux/platform_device.h> 9 #include <linux/mtd/mtd.h> 10 #include <linux/mtd/nand.h> 11 #include <linux/mtd/partitions.h> 12 #include <linux/clk.h> 13 #include <linux/io.h> 14 #include <mach/board.h> 15 #include <mach/cpu.h> 16 17 static struct nand_chip *lhy_nand; 18 static struct mtd_info *lhy_mtd; //定义一个mtd_info结构体 19 20 /* 芯片选择 */ 21 static void lhy_select_chip(struct mtd_info *mtd,int chipnr) 22 { 23 if(chipnr == -1){ 24 /* 取消选中,NFCONT[1]设为0 */ 25 }else{ 26 /* 选中:NFCONT[1]设为1 */ 27 } 28 } 29 //发送命令,地址,数据 30 static void lhy_nand_cmd_ctrl(struct mtd_info *mtd, int cmd, unsigned int ctrl) 31 { 32 if (ctrl & NAND_CLE){ 33 /* 发命令 : NFCMMD=dat*/ 34 writeb(cmd, host->io_base + (1 << host->board->cle)); 35 }else{ 36 writeb(cmd, host->io_base + (1 << host->board->ale)); 37 } 38 } 39 //判断状态 40 static int lhy_dev_ready(struct mtd_info *mtd) 41 { 42 return "NFSTAT 的 bit[0]"; 43 } 44 45 static int lhy_nand_init(void){ 46 47 /* 1.分配一个nand_chip结构体 */ 48 lhy_nand = kzalloc(sizeof(struct nand_chip), GFP_KERNEL); 49 /* 2.设置 */ 50 /* 2.1设置nand_chip是给nand_scan函数用的,如果不知道怎么设置,先看nand_scan怎么用 51 * 它应该提供:选中,发命令,发地址,发数据,读数据,判断状态等功能 52 */ 53 lhy_nand->select_chip = lhy_select_chip; //选中,芯片选择函数 54 lhy_nand->cmd_ctrl = lhy_nand_cmd_ctrl; //负责发送地址,命令 55 lhy_nand->IO_ADDR_R = "NFDATA 的虚拟地址"; 56 lhy_nand->IO_ADDR_R = "NFDATA 的虚拟地址"; 57 lhy_nand->dev_ready = lhy_dev_ready; 58 /* 3.硬件相关的操作 */ 59 60 /* 4.使用nand_scan */ 61 lhy_mtd = kzalloc(sizeof(struct mtd_info), GFP_KERNEL); 62 lhy_mtd->priv = lhy_nand; //私有数据为我们的nand_chip结构体 63 lhy_mtd->owner = THIS_MODULE; 64 65 nand_scan(lhy_mtd,1); //扫描识别nand flash,并且构造mtd,最大芯片个数为1 66 /* 5.add_mtd_partitions */ 67 68 69 return 0; 70 } 71 72 static void lhy_nand_exit(void){ 73 if(lhy_nand) 74 kfree(lhy_nand); 75 if(lhy_mtd) 76 kfree(lhy_mtd); 77 } 78 79 module_init(lhy_nand_init); 80 module_exit(lhy_nand_exit); 81 MODULE_LICENSE("GPL"); 82 83 84 /* 85 S3C2440 U-BOOT 的NAND操作 86 87 1.读取ID 88 选中 NFCONT的bit1设为0 md.1 0x4E000004 1; mw.1 0x4e000004 1 89 发出命令0x90 NFCMMD=0X90 mw.b 0x4E000008 0x90 90 发出地址0x00 NFADD=0X00 mw.b 0x4E00000c 0x00 91 读出数据得到0XEC val=NFDATA md.b 0x4E000010 1 92 读数据得到device code val=NFDATA md.b 0x4E000010 1 93 退出读ID状态 NFCMMD=0xff mw.b 0x4E000008 0xff 94 95 2.读内容 读0地址的数据 96 输入命令: nand dump 0 得到nand 97 98 选中 NFCONT的bit1设为0 md.1 0x4E000004 1; mw.1 0x4e000004 1 99 发出命令0x00 NFCMMD=0X00 mw.b 0x4E000008 0x00 100 101 发出地址0x00 NFADD=0X00 mw.b 0x4E00000c 0x00 102 发出地址0x00 NFADD=0X00 mw.b 0x4E00000c 0x00 103 发出地址0x00 NFADD=0X00 mw.b 0x4E00000c 0x00 104 发出地址0x00 NFADD=0X00 mw.b 0x4E00000c 0x00 105 发出地址0x00 NFADD=0X00 mw.b 0x4E00000c 0x00 106 107 发出命令0x30 NFCMMD=0X00 mw.b 0x4E000008 0x30 108 109 //接下来就是从0地址开始一个字节一个字节的读出数据,和前面nand dump 0 的数据一样 110 读出数据得到0x17 val=NFDATA md.b 0x4E000010 1 111 读出数据得到0x00 val=NFDATA md.b 0x4E000010 1 112 读出数据得到0x00 val=NFDATA md.b 0x4E000010 1 113 114 退出读状态 NFCMMD=0xff mw.b 0x4E000008 0xff 115 116 3.NAND flash 驱动层次 Atmel_nand.c Mtdchar.c 117 块设备: 知道怎么优化 118 NAND Flash协议:知道发什么来读写,擦除,识别 119 硬件相关: 知道怎样发命令/地址,读写数据 120 121 硬件相关: 122 ①分配nand_chip 结构体 123 ②设置nand_chip 124 ③硬件相关设备 125 ④使用 nand_scan / add_mtd_partitions 126 127 */
二、完善前面的程序
1.定义芯片的内存地址,由于其地址是互相相连的所以我们可以使用结构体来省事。
//寄存器结构体 struct lhy_nand_regs{ unsigned long NFCONF ; //偏移地址: S3C2410_NFREG(0x00) unsigned long NFCONT ; //偏移地址: S3C2410_NFREG(0x04) unsigned long NFCMD ; //偏移地址: S3C2410_NFREG(0x08) unsigned long NFADDR ; //偏移地址: S3C2410_NFREG(0x0C) unsigned long NFDATA ; //偏移地址: S3C2410_NFREG(0x10) unsigned long NFECCD0 ; //偏移地址: S3C2410_NFREG(0x14) unsigned long NFECCD1 ; //偏移地址: S3C2410_NFREG(0x18) unsigned long NFECCD ; //偏移地址: S3C2410_NFREG(0x1C) unsigned long NFSTAT ; //偏移地址: S3C2410_NFREG(0x20) unsigned long NFESTAT0 ; //偏移地址: S3C2410_NFREG(0x24) unsigned long NFESTAT1 ; //偏移地址: S3C2410_NFREG(0x28) unsigned long NFMECC0 ; //偏移地址: S3C2410_NFREG(0x2C) unsigned long NFMECC1 ; //偏移地址: S3C2410_NFREG(0x30) unsigned long NFSECC ; //偏移地址: S3C2410_NFREG(0x34) unsigned long NFSBLK ; //偏移地址: S3C2410_NFREG(0x38) unsigned long NFEBLK ; //偏移地址: S3C2410_NFREG(0x3C) };
2.映射寄存器地址内存并其配置
static int lhy_nand_init(void){ /* 1.分配一个nand_chip结构体 */ lhy_nand = kzalloc(sizeof(struct nand_chip), GFP_KERNEL); lhy_nand_regs = ioremap(0x4E000000,sizeof(struct lhy_nand_regs)); //映射寄存器 /* 2.设置 */ /* 2.1设置nand_chip是给nand_scan函数用的,如果不知道怎么设置,先看nand_scan怎么用 * 它应该提供:选中,发命令,发地址,发数据,读数据,判断状态等功能 */ lhy_nand->select_chip = lhy_select_chip; //选中,芯片选择函数 lhy_nand->cmd_ctrl = lhy_nand_cmd_ctrl; //负责发送地址,命令 lhy_nand->IO_ADDR_R = lhy_nand_regs->NFDATA; //读寄存器 lhy_nand->IO_ADDR_W = lhy_nand_regs->NFDATA; //写寄存器 lhy_nand->dev_ready = lhy_dev_ready; //判断状态 /* 3.硬件相关的操作 根据nand flash的手册设置时间参数 * HCLK = 100MHz * TACLS: 发出CLE/ALE之后多长时间发出nWE信号,从NAND手册可知CLE/ALE与nWE可以同时发出,所以TACLS=0 * TWRPH0: nWE的信号的脉冲宽度,HCLK *(TWPRH0 + 1),从NAND手册可知它要>=12ns,所以TWRPH0>=1 * TWRPH1:表示nWE信号变为高电平后,CLE/ALE多长时间才能变为低电平,从手册可知他要>=5ns,所以TWRPH1>=0 */ #define TACLS 0 #define TWRPH0 1 #define TWRPH1 0 lhy_nand_regs->NFCONF |= (TACLS<<12) | (TWRPH0<<8) | (TWRPH1<<4); /* NFCONT的bit1设为1,表示片 选 */ lhy_nand_regs->NFCONT = (1<<1) | (1<<0); /* 4.使用nand_scan */ lhy_mtd = kzalloc(sizeof(struct mtd_info), GFP_KERNEL); lhy_mtd->priv = lhy_nand; //私有数据为我们的nand_chip结构体 lhy_mtd->owner = THIS_MODULE; nand_scan(lhy_mtd,1); //扫描识别nand flash,并且构造mtd,最大芯片个数为1 /* 5.add_mtd_partitions */ return 0; }
附上驱动程序nand_flash2:
1 /* 2 * 参考:linux-2.6.31\drivers\mtd\nand\s3c2410.c atmel_nand.c 3 */ 4 5 #include <linux/slab.h> 6 #include <linux/module.h> 7 #include <linux/moduleparam.h> 8 #include <linux/platform_device.h> 9 #include <linux/mtd/mtd.h> 10 #include <linux/mtd/nand.h> 11 #include <linux/mtd/partitions.h> 12 #include <linux/clk.h> 13 #include <linux/io.h> 14 #include <mach/board.h> 15 #include <mach/cpu.h> 16 17 //寄存器结构体 18 struct lhy_nand_regs{ 19 unsigned long NFCONF ; //偏移地址: S3C2410_NFREG(0x00) 20 unsigned long NFCONT ; //偏移地址: S3C2410_NFREG(0x04) 21 unsigned long NFCMD ; //偏移地址: S3C2410_NFREG(0x08) 22 unsigned long NFADDR ; //偏移地址: S3C2410_NFREG(0x0C) 23 unsigned long NFDATA ; //偏移地址: S3C2410_NFREG(0x10) 24 unsigned long NFECCD0 ; //偏移地址: S3C2410_NFREG(0x14) 25 unsigned long NFECCD1 ; //偏移地址: S3C2410_NFREG(0x18) 26 unsigned long NFECCD ; //偏移地址: S3C2410_NFREG(0x1C) 27 unsigned long NFSTAT ; //偏移地址: S3C2410_NFREG(0x20) 28 unsigned long NFESTAT0 ; //偏移地址: S3C2410_NFREG(0x24) 29 unsigned long NFESTAT1 ; //偏移地址: S3C2410_NFREG(0x28) 30 unsigned long NFMECC0 ; //偏移地址: S3C2410_NFREG(0x2C) 31 unsigned long NFMECC1 ; //偏移地址: S3C2410_NFREG(0x30) 32 unsigned long NFSECC ; //偏移地址: S3C2410_NFREG(0x34) 33 unsigned long NFSBLK ; //偏移地址: S3C2410_NFREG(0x38) 34 unsigned long NFEBLK ; //偏移地址: S3C2410_NFREG(0x3C) 35 }; 36 37 static struct nand_chip *lhy_nand; 38 static struct mtd_info *lhy_mtd; //定义一个mtd_info结构体 39 static struct lhy_nand_regs *lhy_nand_res; //定义寄存器的结构体指针 40 41 /* 芯片选择 */ 42 static void lhy_select_chip(struct mtd_info *mtd,int chipnr) 43 { 44 if(chipnr == -1){ 45 /* 取消选中,NFCONT[1]设为0 */ 46 lhy_nand_regs->NFCONT |= (1<<1); 47 }else{ 48 /* 选中:NFCONT[1]设为1 */ 49 lhy_nand_regs->NFCONT &= ~(1<<1); 50 } 51 } 52 //发送命令,地址,数据 53 static void lhy_nand_cmd_ctrl(struct mtd_info *mtd, int cmd, unsigned int ctrl) 54 { 55 if (ctrl & NAND_CLE){ 56 /* 发命令 : NFCMMD=dat*/ 57 lhy_nand_regs->NFCMD = cmd; 58 }else{ 59 writeb(cmd, host->io_base + (1 << host->board->ale)); 60 lhy_nand_regs->NFADDR = cmd; 61 } 62 } 63 //判断状态 64 static int lhy_dev_ready(struct mtd_info *mtd) 65 { 66 return (lhy_nand_regs->NFSTAT & (1<<0)); 67 } 68 69 static int lhy_nand_init(void){ 70 71 /* 1.分配一个nand_chip结构体 */ 72 lhy_nand = kzalloc(sizeof(struct nand_chip), GFP_KERNEL); 73 74 lhy_nand_regs = ioremap(0x4E000000,sizeof(struct lhy_nand_regs)); //映射寄存器 75 /* 2.设置 */ 76 /* 2.1设置nand_chip是给nand_scan函数用的,如果不知道怎么设置,先看nand_scan怎么用 77 * 它应该提供:选中,发命令,发地址,发数据,读数据,判断状态等功能 78 */ 79 lhy_nand->select_chip = lhy_select_chip; //选中,芯片选择函数 80 lhy_nand->cmd_ctrl = lhy_nand_cmd_ctrl; //负责发送地址,命令 81 lhy_nand->IO_ADDR_R = lhy_nand_regs->NFDATA; //读寄存器 82 lhy_nand->IO_ADDR_W = lhy_nand_regs->NFDATA; //写寄存器 83 lhy_nand->dev_ready = lhy_dev_ready; //判断状态 84 /* 3.硬件相关的操作 根据nand flash的手册设置时间参数 85 * HCLK = 100MHz 86 * TACLS: 发出CLE/ALE之后多长时间发出nWE信号,从NAND手册可知CLE/ALE与nWE可以同时发出,所以TACLS=0 87 * TWRPH0: nWE的信号的脉冲宽度,HCLK *(TWPRH0 + 1),从NAND手册可知它要>=12ns,所以TWRPH0>=1 88 * TWRPH1:表示nWE信号变为高电平后,CLE/ALE多长时间才能变为低电平,从手册可知他要>=5ns,所以TWRPH1>=0 89 */ 90 #define TACLS 0 91 #define TWRPH0 1 92 #define TWRPH1 0 93 lhy_nand_regs->NFCONF |= (TACLS<<12) | (TWRPH0<<8) | (TWRPH1<<4); 94 95 /* NFCONT的bit1设为1,表示片 选 */ 96 lhy_nand_regs->NFCONT = (1<<1) | (1<<0); 97 /* 4.使用nand_scan */ 98 lhy_mtd = kzalloc(sizeof(struct mtd_info), GFP_KERNEL); 99 lhy_mtd->priv = lhy_nand; //私有数据为我们的nand_chip结构体 100 lhy_mtd->owner = THIS_MODULE; 101 102 nand_scan(lhy_mtd,1); //扫描识别nand flash,并且构造mtd,最大芯片个数为1 103 /* 5.add_mtd_partitions */ 104 105 return 0; 106 } 107 108 static void lhy_nand_exit(void){ 109 if(lhy_nand) 110 kfree(lhy_nand); 111 if(lhy_mtd) 112 kfree(lhy_mtd); 113 if(lhy_nand_regs) 114 iounmap(lhy_nand_regs); 115 } 116 117 module_init(lhy_nand_init); 118 module_exit(lhy_nand_exit); 119 MODULE_LICENSE("GPL"); 120 121 122 /* 123 S3C2440 U-BOOT 的NAND操作 124 125 1.读取ID 126 选中 NFCONT的bit1设为0 md.1 0x4E000004 1; mw.1 0x4e000004 1 127 发出命令0x90 NFCMMD=0X90 mw.b 0x4E000008 0x90 128 发出地址0x00 NFADD=0X00 mw.b 0x4E00000c 0x00 129 读出数据得到0XEC val=NFDATA md.b 0x4E000010 1 130 读数据得到device code val=NFDATA md.b 0x4E000010 1 131 退出读ID状态 NFCMMD=0xff mw.b 0x4E000008 0xff 132 133 2.读内容 读0地址的数据 134 输入命令: nand dump 0 得到nand 135 136 选中 NFCONT的bit1设为0 md.1 0x4E000004 1; mw.1 0x4e000004 1 137 发出命令0x00 NFCMMD=0X00 mw.b 0x4E000008 0x00 138 139 发出地址0x00 NFADD=0X00 mw.b 0x4E00000c 0x00 140 发出地址0x00 NFADD=0X00 mw.b 0x4E00000c 0x00 141 发出地址0x00 NFADD=0X00 mw.b 0x4E00000c 0x00 142 发出地址0x00 NFADD=0X00 mw.b 0x4E00000c 0x00 143 发出地址0x00 NFADD=0X00 mw.b 0x4E00000c 0x00 144 145 发出命令0x30 NFCMMD=0X00 mw.b 0x4E000008 0x30 146 147 //接下来就是从0地址开始一个字节一个字节的读出数据,和前面nand dump 0 的数据一样 148 读出数据得到0x17 val=NFDATA md.b 0x4E000010 1 149 读出数据得到0x00 val=NFDATA md.b 0x4E000010 1 150 读出数据得到0x00 val=NFDATA md.b 0x4E000010 1 151 152 退出读状态 NFCMMD=0xff mw.b 0x4E000008 0xff 153 154 3.NAND flash 驱动层次 Atmel_nand.c Mtdchar.c 155 块设备: 知道怎么优化 156 NAND Flash协议:知道发什么来读写,擦除,识别 157 硬件相关: 知道怎样发命令/地址,读写数据 158 159 硬件相关: 160 ①分配nand_chip 结构体 161 ②设置nand_chip 162 ③硬件相关设备 163 ④使用 nand_scan / add_mtd_partitions 164 165 166 */
三、增加ECC校验码
前面的程序是可以直接用的,但是加载驱动时会报ECC校验错误。
在flash设备中,每一页都有64b是不参与编址的,这一块是不参与统一编址OBB(out of bank)。
原因:nand flash内存中,数据很容易发生位反转,为了防止数据发生错误,引入了ECC校验。
解决方案:写一页数据时,这一页的数据生成ECC校验码,然后把ECC校验码写入OBB中。
读取数据时:首先读取page,读OOB里的ECC,根据page的内容实时计算ECC,看是否与OOB中的
ECC相同,若是相同则说明数据没有错误,否则通过ECC校验码也可以算出是哪一位发生错误。
实现方法:可以设置为软件实现或者硬件实现,只要在nand_chip中的ECC结构体的mode中设置为NAND_ECC_SOFT,就是开启了软件ECC校验。
nand_chip->ecc.mode = NAND_ECC_SOFT; /*使能ECC校验码 enable ECC */
附上驱动程序nand_flash3.c
1 /* 2 * 参考:linux-2.6.31\drivers\mtd\nand\s3c2410.c atmel_nand.c 3 */ 4 5 #include <linux/slab.h> 6 #include <linux/module.h> 7 #include <linux/moduleparam.h> 8 #include <linux/platform_device.h> 9 #include <linux/mtd/mtd.h> 10 #include <linux/mtd/nand.h> 11 #include <linux/mtd/partitions.h> 12 #include <linux/clk.h> 13 #include <linux/io.h> 14 #include <mach/board.h> 15 #include <mach/cpu.h> 16 17 //寄存器结构体 18 struct lhy_nand_regs{ 19 unsigned long NFCONF ; //偏移地址: S3C2410_NFREG(0x00) 20 unsigned long NFCONT ; //偏移地址: S3C2410_NFREG(0x04) 21 unsigned long NFCMD ; //偏移地址: S3C2410_NFREG(0x08) 22 unsigned long NFADDR ; //偏移地址: S3C2410_NFREG(0x0C) 23 unsigned long NFDATA ; //偏移地址: S3C2410_NFREG(0x10) 24 unsigned long NFECCD0 ; //偏移地址: S3C2410_NFREG(0x14) 25 unsigned long NFECCD1 ; //偏移地址: S3C2410_NFREG(0x18) 26 unsigned long NFECCD ; //偏移地址: S3C2410_NFREG(0x1C) 27 unsigned long NFSTAT ; //偏移地址: S3C2410_NFREG(0x20) 28 unsigned long NFESTAT0 ; //偏移地址: S3C2410_NFREG(0x24) 29 unsigned long NFESTAT1 ; //偏移地址: S3C2410_NFREG(0x28) 30 unsigned long NFMECC0 ; //偏移地址: S3C2410_NFREG(0x2C) 31 unsigned long NFMECC1 ; //偏移地址: S3C2410_NFREG(0x30) 32 unsigned long NFSECC ; //偏移地址: S3C2410_NFREG(0x34) 33 unsigned long NFSBLK ; //偏移地址: S3C2410_NFREG(0x38) 34 unsigned long NFEBLK ; //偏移地址: S3C2410_NFREG(0x3C) 35 }; 36 37 static struct nand_chip *lhy_nand; 38 static struct mtd_info *lhy_mtd; //定义一个mtd_info结构体 39 static struct lhy_nand_regs *lhy_nand_res; //定义寄存器的结构体指针 40 41 /* 芯片选择 */ 42 static void lhy_select_chip(struct mtd_info *mtd,int chipnr) 43 { 44 if(chipnr == -1){ 45 /* 取消选中,NFCONT[1]设为0 */ 46 lhy_nand_regs->NFCONT |= (1<<1); 47 }else{ 48 /* 选中:NFCONT[1]设为1 */ 49 lhy_nand_regs->NFCONT &= ~(1<<1); 50 } 51 } 52 //发送命令,地址,数据 53 static void lhy_nand_cmd_ctrl(struct mtd_info *mtd, int cmd, unsigned int ctrl) 54 { 55 if (ctrl & NAND_CLE){ 56 /* 发命令 : NFCMMD=dat*/ 57 lhy_nand_regs->NFCMD = cmd; 58 }else{ 59 writeb(cmd, host->io_base + (1 << host->board->ale)); 60 lhy_nand_regs->NFADDR = cmd; 61 } 62 } 63 //判断状态 64 static int lhy_dev_ready(struct mtd_info *mtd) 65 { 66 return (lhy_nand_regs->NFSTAT & (1<<0)); 67 } 68 69 static int lhy_nand_init(void){ 70 71 /* 1.分配一个nand_chip结构体 */ 72 lhy_nand = kzalloc(sizeof(struct nand_chip), GFP_KERNEL); 73 74 lhy_nand_regs = ioremap(0x4E000000,sizeof(struct lhy_nand_regs)); //映射寄存器 75 /* 2.设置 */ 76 /* 2.1设置nand_chip是给nand_scan函数用的,如果不知道怎么设置,先看nand_scan怎么用 77 * 它应该提供:选中,发命令,发地址,发数据,读数据,判断状态等功能 78 */ 79 lhy_nand->select_chip = lhy_select_chip; //选中,芯片选择函数 80 lhy_nand->cmd_ctrl = lhy_nand_cmd_ctrl; //负责发送地址,命令 81 lhy_nand->IO_ADDR_R = lhy_nand_regs->NFDATA; //读寄存器 82 lhy_nand->IO_ADDR_W = lhy_nand_regs->NFDATA; //写寄存器 83 lhy_nand->dev_ready = lhy_dev_ready; //判断状态 84 nand_chip->ecc.mode = NAND_ECC_SOFT; /*使能ECC校验码 enable ECC */ 85 86 /* 3.硬件相关的操作 根据nand flash的手册设置时间参数 87 * HCLK = 100MHz 88 * TACLS: 发出CLE/ALE之后多长时间发出nWE信号,从NAND手册可知CLE/ALE与nWE可以同时发出,所以TACLS=0 89 * TWRPH0: nWE的信号的脉冲宽度,HCLK *(TWPRH0 + 1),从NAND手册可知它要>=12ns,所以TWRPH0>=1 90 * TWRPH1:表示nWE信号变为高电平后,CLE/ALE多长时间才能变为低电平,从手册可知他要>=5ns,所以TWRPH1>=0 91 */ 92 #define TACLS 0 93 #define TWRPH0 1 94 #define TWRPH1 0 95 lhy_nand_regs->NFCONF |= (TACLS<<12) | (TWRPH0<<8) | (TWRPH1<<4); 96 97 /* NFCONT的bit1设为1,表示片 选 */ 98 lhy_nand_regs->NFCONT = (1<<1) | (1<<0); 99 /* 4.使用nand_scan */ 100 lhy_mtd = kzalloc(sizeof(struct mtd_info), GFP_KERNEL); 101 lhy_mtd->priv = lhy_nand; //私有数据为我们的nand_chip结构体 102 lhy_mtd->owner = THIS_MODULE; 103 104 nand_scan(lhy_mtd,1); //扫描识别nand flash,并且构造mtd,最大芯片个数为1 105 /* 5.add_mtd_partitions 添加分区 */ 106 //add_mtd_partitions(tiny_nand_mtd, tiny_nand_part, 3); 107 return 0; 108 } 109 110 static void lhy_nand_exit(void){ 111 if(lhy_nand) 112 kfree(lhy_nand); 113 if(lhy_mtd) 114 kfree(lhy_mtd); 115 if(lhy_nand_regs) 116 iounmap(lhy_nand_regs); 117 } 118 119 module_init(lhy_nand_init); 120 module_exit(lhy_nand_exit); 121 MODULE_LICENSE("GPL"); 122 123 124 /* 125 S3C2440 U-BOOT 的NAND操作 126 127 1.读取ID 128 选中 NFCONT的bit1设为0 md.1 0x4E000004 1; mw.1 0x4e000004 1 129 发出命令0x90 NFCMMD=0X90 mw.b 0x4E000008 0x90 130 发出地址0x00 NFADD=0X00 mw.b 0x4E00000c 0x00 131 读出数据得到0XEC val=NFDATA md.b 0x4E000010 1 132 读数据得到device code val=NFDATA md.b 0x4E000010 1 133 退出读ID状态 NFCMMD=0xff mw.b 0x4E000008 0xff 134 135 2.读内容 读0地址的数据 136 输入命令: nand dump 0 得到nand 137 138 选中 NFCONT的bit1设为0 md.1 0x4E000004 1; mw.1 0x4e000004 1 139 发出命令0x00 NFCMMD=0X00 mw.b 0x4E000008 0x00 140 141 发出地址0x00 NFADD=0X00 mw.b 0x4E00000c 0x00 142 发出地址0x00 NFADD=0X00 mw.b 0x4E00000c 0x00 143 发出地址0x00 NFADD=0X00 mw.b 0x4E00000c 0x00 144 发出地址0x00 NFADD=0X00 mw.b 0x4E00000c 0x00 145 发出地址0x00 NFADD=0X00 mw.b 0x4E00000c 0x00 146 147 发出命令0x30 NFCMMD=0X00 mw.b 0x4E000008 0x30 148 149 //接下来就是从0地址开始一个字节一个字节的读出数据,和前面nand dump 0 的数据一样 150 读出数据得到0x17 val=NFDATA md.b 0x4E000010 1 151 读出数据得到0x00 val=NFDATA md.b 0x4E000010 1 152 读出数据得到0x00 val=NFDATA md.b 0x4E000010 1 153 154 退出读状态 NFCMMD=0xff mw.b 0x4E000008 0xff 155 156 3.NAND flash 驱动层次 Atmel_nand.c Mtdchar.c 157 块设备: 知道怎么优化 158 NAND Flash协议:知道发什么来读写,擦除,识别 159 硬件相关: 知道怎样发命令/地址,读写数据 160 161 硬件相关: 162 ①分配nand_chip 结构体 163 ②设置nand_chip 164 ③硬件相关设备 165 ④使用 nand_scan / add_mtd_partitions 166 167 168 4.ECC校验码 64byte的OOB内存 169 flash每一页都有64B不参与编址,被称为OOB(out of bank) 170 原因是,nand flash的内存中,数据很容易发生位反转,为了防止数据出错,引入了ECC校验, 171 解决:①写一页的数据 ②用这一页的内容生成ECC校验码 ③把ECC校验码写入OOB中 172 读取:①读page ②读OOB里的ECC ③根据page的内容算ECC 173 ④将算的ECC校验码和读取OOB中的校验码进行校验和校准,可以算出哪一位发生错误。 174 ECC校验码生成:可以硬件也可以软件 175 176 */
四、增加分区挂接
要增加分区的话,首先要定义mtd_partition结构体,里面定义了分区的名字,起始地址,以及分区的大小等参数。
1 //定义nand flash的分区 2 static struct mtd_partition lhy_nand_part[] = { 3 [0] = { 4 .name = "bootloader", 5 .size = 0x40000, 6 .offset = 0, 7 }, 8 [1] = { 9 .name = "params", 10 .offset = MTDPART_OFS_APPEND, //大小紧跟真前面这个分区 11 .size = 0x20000, 12 }, 13 [2] = { 14 .name = "kernel", 15 .offset = MTDPART_OFS_APPEND, //大小紧跟真前面这个分区 16 .size = 0x200000, 17 }, 18 [3] = { 19 .name = "root", 20 .offset = MTDPART_OFS_APPEND, 21 .size = MTDPART_SIZ_FULL, //剩余的所有大小 22 }, 23 };
接着就是在init函数中添加分区结构体。
1 /* 5.add_mtd_partitions 添加分区 */ 2 //如果想整块flash只作为一个分区,使用add_mtd_device就够了 3 //add_mtd_device(lhy_mtd); 4 //如果要创建多个分区的话,那么就要使用add_mtd_partitions 5 add_mtd_partitions(lhy_nand_mtd, lhy_nand_part, 3);
附上驱动程序nand_flash4.c
1 /* 2 * 参考:linux-2.6.31\drivers\mtd\nand\s3c2410.c atmel_nand.c 3 */ 4 5 #include <linux/slab.h> 6 #include <linux/module.h> 7 #include <linux/moduleparam.h> 8 #include <linux/platform_device.h> 9 #include <linux/mtd/mtd.h> 10 #include <linux/mtd/nand.h> 11 #include <linux/mtd/partitions.h> 12 #include <linux/clk.h> 13 #include <linux/io.h> 14 #include <mach/board.h> 15 #include <mach/cpu.h> 16 17 //寄存器结构体 18 struct lhy_nand_regs{ 19 unsigned long NFCONF ; //偏移地址: S3C2410_NFREG(0x00) 20 unsigned long NFCONT ; //偏移地址: S3C2410_NFREG(0x04) 21 unsigned long NFCMD ; //偏移地址: S3C2410_NFREG(0x08) 22 unsigned long NFADDR ; //偏移地址: S3C2410_NFREG(0x0C) 23 unsigned long NFDATA ; //偏移地址: S3C2410_NFREG(0x10) 24 unsigned long NFECCD0 ; //偏移地址: S3C2410_NFREG(0x14) 25 unsigned long NFECCD1 ; //偏移地址: S3C2410_NFREG(0x18) 26 unsigned long NFECCD ; //偏移地址: S3C2410_NFREG(0x1C) 27 unsigned long NFSTAT ; //偏移地址: S3C2410_NFREG(0x20) 28 unsigned long NFESTAT0 ; //偏移地址: S3C2410_NFREG(0x24) 29 unsigned long NFESTAT1 ; //偏移地址: S3C2410_NFREG(0x28) 30 unsigned long NFMECC0 ; //偏移地址: S3C2410_NFREG(0x2C) 31 unsigned long NFMECC1 ; //偏移地址: S3C2410_NFREG(0x30) 32 unsigned long NFSECC ; //偏移地址: S3C2410_NFREG(0x34) 33 unsigned long NFSBLK ; //偏移地址: S3C2410_NFREG(0x38) 34 unsigned long NFEBLK ; //偏移地址: S3C2410_NFREG(0x3C) 35 }; 36 37 static struct nand_chip *lhy_nand; 38 static struct mtd_info *lhy_mtd; //定义一个mtd_info结构体 39 static struct lhy_nand_regs *lhy_nand_res; //定义寄存器的结构体指针 40 41 //定义nand flash的分区 42 static struct mtd_partition lhy_nand_part[] = { 43 [0] = { 44 .name = "bootloader", 45 .size = 0x40000, 46 .offset = 0, 47 }, 48 [1] = { 49 .name = "params", 50 .offset = MTDPART_OFS_APPEND, //大小紧跟真前面这个分区 51 .size = 0x20000, 52 }, 53 [2] = { 54 .name = "kernel", 55 .offset = MTDPART_OFS_APPEND, //大小紧跟真前面这个分区 56 .size = 0x200000, 57 }, 58 [3] = { 59 .name = "root", 60 .offset = MTDPART_OFS_APPEND, 61 .size = MTDPART_SIZ_FULL, //剩余的所有大小 62 }, 63 }; 64 65 /* 芯片选择 */ 66 static void lhy_select_chip(struct mtd_info *mtd,int chipnr) 67 { 68 if(chipnr == -1){ 69 /* 取消选中,NFCONT[1]设为0 */ 70 lhy_nand_regs->NFCONT |= (1<<1); 71 }else{ 72 /* 选中:NFCONT[1]设为1 */ 73 lhy_nand_regs->NFCONT &= ~(1<<1); 74 } 75 } 76 //发送命令,地址,数据 77 static void lhy_nand_cmd_ctrl(struct mtd_info *mtd, int cmd, unsigned int ctrl) 78 { 79 if (ctrl & NAND_CLE){ 80 /* 发命令 : NFCMMD=dat*/ 81 lhy_nand_regs->NFCMD = cmd; 82 }else{ 83 writeb(cmd, host->io_base + (1 << host->board->ale)); 84 lhy_nand_regs->NFADDR = cmd; 85 } 86 } 87 //判断状态 88 static int lhy_dev_ready(struct mtd_info *mtd) 89 { 90 return (lhy_nand_regs->NFSTAT & (1<<0)); 91 } 92 93 static int lhy_nand_init(void){ 94 95 /* 1.分配一个nand_chip结构体 */ 96 lhy_nand = kzalloc(sizeof(struct nand_chip), GFP_KERNEL); 97 98 lhy_nand_regs = ioremap(0x4E000000,sizeof(struct lhy_nand_regs)); //映射寄存器 99 /* 2.设置 */ 100 /* 2.1设置nand_chip是给nand_scan函数用的,如果不知道怎么设置,先看nand_scan怎么用 101 * 它应该提供:选中,发命令,发地址,发数据,读数据,判断状态等功能 102 */ 103 lhy_nand->select_chip = lhy_select_chip; //选中,芯片选择函数 104 lhy_nand->cmd_ctrl = lhy_nand_cmd_ctrl; //负责发送地址,命令 105 lhy_nand->IO_ADDR_R = lhy_nand_regs->NFDATA; //读寄存器 106 lhy_nand->IO_ADDR_W = lhy_nand_regs->NFDATA; //写寄存器 107 lhy_nand->dev_ready = lhy_dev_ready; //判断状态 108 nand_chip->ecc.mode = NAND_ECC_SOFT; /*使能软件ECC校验码 enable ECC */ 109 110 /* 3.硬件相关的操作 根据nand flash的手册设置时间参数 111 * HCLK = 100MHz 112 * TACLS: 发出CLE/ALE之后多长时间发出nWE信号,从NAND手册可知CLE/ALE与nWE可以同时发出,所以TACLS=0 113 * TWRPH0: nWE的信号的脉冲宽度,HCLK *(TWPRH0 + 1),从NAND手册可知它要>=12ns,所以TWRPH0>=1 114 * TWRPH1:表示nWE信号变为高电平后,CLE/ALE多长时间才能变为低电平,从手册可知他要>=5ns,所以TWRPH1>=0 115 */ 116 #define TACLS 0 117 #define TWRPH0 1 118 #define TWRPH1 0 119 lhy_nand_regs->NFCONF |= (TACLS<<12) | (TWRPH0<<8) | (TWRPH1<<4); 120 121 /* NFCONT的bit1设为1,表示片 选 */ 122 lhy_nand_regs->NFCONT = (1<<1) | (1<<0); 123 /* 4.使用nand_scan */ 124 lhy_mtd = kzalloc(sizeof(struct mtd_info), GFP_KERNEL); 125 lhy_mtd->priv = lhy_nand; //私有数据为我们的nand_chip结构体 126 lhy_mtd->owner = THIS_MODULE; 127 128 nand_scan(lhy_mtd,1); //扫描识别nand flash,并且构造mtd,最大芯片个数为1 129 /* 5.add_mtd_partitions 添加分区 */ 130 //如果想整块flash只作为一个分区,使用add_mtd_device就够了 131 //add_mtd_device(lhy_mtd); 132 //如果要创建多个分区的话,那么就要使用add_mtd_partitions 133 add_mtd_partitions(lhy_nand_mtd, lhy_nand_part, 3); 134 return 0; 135 } 136 137 static void lhy_nand_exit(void){ 138 del_mtd_partitions(lhy_nand_mtd); 139 if(lhy_nand) 140 kfree(lhy_nand); 141 if(lhy_mtd) 142 kfree(lhy_mtd); 143 if(lhy_nand_regs) 144 iounmap(lhy_nand_regs); 145 } 146 147 module_init(lhy_nand_init); 148 module_exit(lhy_nand_exit); 149 MODULE_LICENSE("GPL"); 150 151 152 /* 153 S3C2440 U-BOOT 的NAND操作 154 155 1.读取ID 156 选中 NFCONT的bit1设为0 md.1 0x4E000004 1; mw.1 0x4e000004 1 157 发出命令0x90 NFCMMD=0X90 mw.b 0x4E000008 0x90 158 发出地址0x00 NFADD=0X00 mw.b 0x4E00000c 0x00 159 读出数据得到0XEC val=NFDATA md.b 0x4E000010 1 160 读数据得到device code val=NFDATA md.b 0x4E000010 1 161 退出读ID状态 NFCMMD=0xff mw.b 0x4E000008 0xff 162 163 2.读内容 读0地址的数据 164 输入命令: nand dump 0 得到nand 165 166 选中 NFCONT的bit1设为0 md.1 0x4E000004 1; mw.1 0x4e000004 1 167 发出命令0x00 NFCMMD=0X00 mw.b 0x4E000008 0x00 168 169 发出地址0x00 NFADD=0X00 mw.b 0x4E00000c 0x00 170 发出地址0x00 NFADD=0X00 mw.b 0x4E00000c 0x00 171 发出地址0x00 NFADD=0X00 mw.b 0x4E00000c 0x00 172 发出地址0x00 NFADD=0X00 mw.b 0x4E00000c 0x00 173 发出地址0x00 NFADD=0X00 mw.b 0x4E00000c 0x00 174 175 发出命令0x30 NFCMMD=0X00 mw.b 0x4E000008 0x30 176 177 //接下来就是从0地址开始一个字节一个字节的读出数据,和前面nand dump 0 的数据一样 178 读出数据得到0x17 val=NFDATA md.b 0x4E000010 1 179 读出数据得到0x00 val=NFDATA md.b 0x4E000010 1 180 读出数据得到0x00 val=NFDATA md.b 0x4E000010 1 181 182 退出读状态 NFCMMD=0xff mw.b 0x4E000008 0xff 183 184 3.NAND flash 驱动层次 Atmel_nand.c Mtdchar.c 185 块设备: 知道怎么优化 186 NAND Flash协议:知道发什么来读写,擦除,识别 187 硬件相关: 知道怎样发命令/地址,读写数据 188 189 硬件相关: 190 ①分配nand_chip 结构体 191 ②设置nand_chip 192 ③硬件相关设备 193 ④使用 nand_scan / add_mtd_partitions 194 195 196 4.ECC校验码 64byte的OOB内存 197 flash每一页都有64B不参与编址,被称为OOB(out of bank) 198 原因是,nand flash的内存中,数据很容易发生位反转,为了防止数据出错,引入了ECC校验, 199 解决:①写一页的数据 ②用这一页的内容生成ECC校验码 ③把ECC校验码写入OOB中 200 读取:①读page ②读OOB里的ECC ③根据page的内容算ECC 201 ④将算的ECC校验码和读取OOB中的校验码进行校验和校准,可以算出哪一位发生错误。 202 ECC校验码生成:可以硬件也可以软件 203 204 5.测试3th 205 nfs挂载 206 207 1.去掉内核自带的NAND FLASH驱动 208 -->Device Drivers 209 -->Memory Technology Device (MTD) support 210 -->NAND Device Support 211 < > NAND Flash support for s3c2410 SoC 212 make uImage 213 2.保存以前的根文件系统的bootargs 214 nfs 30000000 192.168.1.5:/work/nfs_root/uImage_nonand 215 set bootargs console=ttySAC0 root=/dev/nfs nfsroot=192.168.1.105:/work/nfs_root/first_fs ip=192.168.1.17:192.168.1.5:192.168.1.1:255.255.255.0::eth0:off 216 217 3.ls /dev/mtd* //没有分区 218 4.insmod lhy_nand.ko 219 5.ls /dev/mtd* //四个分区,总共有四对设备节点,只读与可写 220 6.格式化nand flash,工具:mtd_utils-06.7.23.tar.bz2 221 进入utils目录,修改Makefile,#CROSS=ARM-linux 修改为 CROSS=ARM-linux,把#号去掉 222 make 223 7.cp flash_erase flash_eraseall 拷贝到网络文件系统即可 224 8.flah_eraseall /dev/mtd2 225 9.挂接mount -t yaffs /dev/mtdblock3 /mnt/,可以在里面创建文件 226 10.接着重新启动开发板,重新装载模块,挂接,看看文件是否还在里面 227 228 229 */