1.先看硬件原理图,nand是怎样接到主控芯片的哪里,nand芯片的各个管脚是什么意义?各个管脚要怎样配合才可以访问nand;
主控芯片的nand控制器的RnB管脚接到---->nand芯片的R/B管脚,这个管脚是判断nand芯片是否正忙的管脚,主控芯片通过读nand控制器的RnB为0说明nand正忙(读寄存器NFSTAT的bit0);
主控芯片的nand控制器的CLE管脚接到---->nand芯片的非CLE管脚,当主控芯片的CLE管脚拉低时,出现在data0~data7的数据是命令;(把命令值写到NFCMMD就是拉低CLE了)
主控芯片的nand控制器的ALE管脚接到---->nand芯片的非ALE管脚,当主控芯片的ALE管脚拉低时,出现在data0~data7的数据是地址;(把地址值写到NFADDR就是拉低ALE了)
主控芯片的nand控制器的nFCE管脚接到---->nand芯片的非CE管脚,当主控芯片的CE管脚拉低时,表示选中nand,也就是片选管脚;(操作寄存器NFCONT的bit1)
主控芯片的nand控制器的nFWE管脚接到---->nand芯片的非WE管脚,是nand flash写使能管脚,说明数据传输的方向是(主控芯片->nand);将数据写到NFDATA寄存器就自动拉低nFWE管脚了
主控芯片的nand控制器的nFRE管脚接到---->nand芯片的非RE管脚,是nand flash读使能管脚,说明数据传输的方向是(主控芯片<-nand);读NFDATA寄存器就自动拉低nFRE管脚了
小结:nand的硬件协议就是各个管脚是怎样配合实现对nand的访问的,但是通过主控芯片的nand控制器访问时,有些步骤已经被合成了,驱动不用做那么多个步骤,比如nFWE,nFRE管脚驱动的体现就不是那么明显;
写一个Linux nand设备驱动,要参考\\10.150.50.230\zhangjiaqi\linux-5.8.5\Documentation\driver-api\mtdnand.rst
nand driver(driver/mtd/nand/raw/s3c_nand.c) ---> kernel's MTD subsystem <--- nand device(nand闪存芯片)
读状态正忙函数:
return 0, if the device is busy (R/B pin is low);
return 1, if the device is ready (R/B pin is high).
如果nand芯片并没有R/B接口:那么the function pointer this->legacy.dev_ready is set to NULL.
nand_chip与mtd_info会互相绑定的;
chip->options 要设置为空,空就代码为8bit设备,NAND_BUSWIDTH_16代表16bit模式
!chip->legacy.cmdfunc//这个函数可能我们要提供,因为默认的是512小页的,而我们的设备是2048大页的;
chip->legacy.select_chip//这个片选函数要实现
chip->legacy.dev_ready(chip)//这个函数要提供
chip->legacy.read_byte //这个函数不用提供,已经有现成的,只需提供chip->legacy.IO_ADDR_R就行
chip->legacy.write_buf //这个函数不用提供,已经有现成的,只需提供chip->legacy.IO_ADDR_W就行
chip->legacy.write_byte//这个函数不用提供,默认的能用,只需提供chip->legacy.IO_ADDR_W就行
mtd->writesize这个要设置为512或者2048
//设备树节点:
提供属性nand-bus-width=8,等于8或者16
nand-is-boot-medium这个bool型属性不知要不要设置,如果是从nand启动系统就要提供这个属性。
nand-on-flash-bbt:这个bool型属性可能要提供,如果是使用默认的坏块表就要提供这个属性。
nand-ecc-mode="soft",这个要提供,表示chip->ecc.mode = NAND_ECC_SOFT;
nand-ecc-algo="hamming",这个要提供。
nand-ecc-strength=数值,这个要提供表示ecc的长度。不知道要不要提供
nand-ecc-step-size=数值,不知道要不要提供
nand-ecc-maximize,这个bool型的属性不知道要不要提供
内核会根据标准的read id操作(发出0X90cmd,在发出0x00地址)得到真实的厂家ID存在maf_id变量里,再用maf_id与内核所支持的nand的id进行
数值对比,内核所支持的nand都在linux-5.8.5\drivers\mtd\nand\raw\nand_ids.c,如果这个nand_ids.c里没有你的nand芯片,就要往这里添加
你的nand芯片的信息结构体;例如 {NAND_MFR_SAMSUNG, "Samsung", &samsung_nand_manuf_ops},第一个变量NAND_MFR_SAMSUNG是个宏定义,表示读回来的厂家ID;
根据dev_id遍历内核所支持的所有nand的信息,得到一个nand的细节信息结构体(这个结构体含有nand的name,chip_size,),
这个结构体在\\10.150.50.230\zhangjiaqi\linux-5.8.5\drivers\mtd\nand\raw\nand_ids.c,要往这个文件添加自己nand硬件的信息
总的来说有两处要对比的都在\\10.150.50.230\zhangjiaqi\linux-5.8.5\drivers\mtd\nand\raw\nand_ids.c,这个文件含有两个结构体
分别是struct nand_manufacturer,struct nand_flash_dev;这两个结构体共同决定了内核支持的某一款nand,这里面代表了linux内核所支持的
所有nand芯片,里面有两个nand芯片结构体一个使用dev_id对比的,另一个是用Manufacturer_id对比的;
/**
*根据ONFI标准和JEDEC标准填充nand的内核规格信息例如:page_size,ease_size,oob_size
*如果不符合上面两个标准则该款nand芯片无法使用Linux内核提供的这套标准的nand核心层协议
*后面也无法成功注册mtd设备;如果是不符合上面两个标准的nand只能自己实现mtd->_write,
*mtd->_read,mtd->_erase,然后手动注册mtd;
*/
nand_scan
nand_scan_with_ids
nand_scan_ident
nand_detect
nand_onfi_detect //检查是否符合ONFI标准
nand_jedec_detect //检查是否符合JEDEC标准
/**
*设置时序的函数
* Configure the data interface in SDR mode and set the timings to timing mode 0.
*/
nand_scan
nand_scan_with_ids
nand_scan_ident
nand_detect
nand_reset
chip->controller->ops->setup_data_interface(chip, chipnr,&chip->data_interface);//调用到设备驱动实现的函数
/*
* 可以设置硬件初始化相关的
*/
nand_scan
nand_scan_with_ids
nand_attach
chip->controller->ops->attach_chip(chip);//调用到设备驱动实现的chip->controller->ops->attach_chip(chip)函数(挂接函数),一般与(卸载函数)chip->controller->ops->detach_chip(chip)配套使用
总结及全面源码剖析:
用内核的nand flash协议写nand flsah设备驱动,主要有三个点;
1. struct nand_chip,
2. nand_scan函数
3. mtd_device_register函数
构造nand_chip时并不是要填充这个结构体的所有函数的,要填充哪些函数完全要看nand_scan这个函数,下面进行详细分析;
nand_scan(struct nand_chip *chip, unsigned int max_chips)//chip为设备驱动申请的nand_chip结构体,max_chips为多少个nand_chip芯片;
nand_scan_with_ids
nand_scan_ident//nand_scan第一阶段
nanddev_get_memorg(&chip->base);//获取chip->base->memorg,有关page_size,oob_size的信息,这个memorg会在下面补充填充
nand_dt_init(chip);//设备树要怎么写就要看这个函数
nand_set_defaults//这个函数非常核心;
nand_legacy_set_defaults//内核提供了一些可以通用的函数,如果设备驱动没有提供就用这些默认的,但是有三个函数是必须提供的chip->legacy.cmd_ctrl,chip->legacy.dev_ready(chip),chip->legacy.select_chip这三个是必须提供的。
if (!chip->legacy.cmdfunc)
chip->legacy.cmdfunc = nand_command;/*这个默认函数是适用于小页512byte的*/
/* check, if a user supplied wait function given */
if (chip->legacy.waitfunc == NULL)
chip->legacy.waitfunc = nand_wait;/*调用设备驱动的chip->legacy.dev_ready(chip)等待nand操作完成*/
if (!chip->legacy.select_chip)
chip->legacy.select_chip = nand_select_chip;/*这个默认的片选函数里面是空的,无法选中,要设备驱动提供*/
//在个函数的后半段还会提供有关nand的读写一个byte,和读写buf的函数,要区分硬件的位宽是8还是16(位宽在设备树里指定,在nand_dt_init里提取填充);一般都通用,如果通用就在设备层自己写代替这里的;
nand_detect(chip, table);//设置时序,这函数非常核心
nand_reset(chip, 0);//设置时序之后复位
nand_reset_data_interface
chip->controller->ops->setup_data_interface//设备驱动要提供chip->controller,并且要在里面设置时序;
nand_readid_op//读设备ID
nand_get_manufacturer//根据读回来的厂家ID在nand_ids.c找到nand的形容结构体struct nand_manufacturer,要支持此nand,必须在文件中添加有关自己nand 芯片的信息;
type = nand_flash_ids;
if (dev_id == type->dev_id)//走这里,找到符合我们的硬件的nand细节信息结构体struct nand_flash_dev并且存在type里了
//检查芯片是否符合ONFI,根据硬件填充memorg
//检查芯片是否符合JEDEC标准,根据硬件填充memorg
nand_legacy_adjust_cmdfunc(chip);//这里非常核心,这里调整为大页nand的发命令函数
nand_attach
chip->controller->ops->attach_chip(chip)//调用到设备驱动实现的函数attach_chip,当驱动与设备挂接上时会调用这个函数,在nand_cleanup会调用卸载函数chip->controller->ops->detach_chip(chip)
nand_scan_tail(chip);//nand_scan第二阶段,构造mtd的操作函数
nand_manufacturer_init(chip);//如果drivers\mtd\nand\raw\nand_ids.c里记录的这款nand自带init函数就在这里调用,我们的nand内核已经支持,里面自带init,所以这里会调用,主要是如果这款芯片的page_size>512,就是对chip->option作大页的标记,以及slc判断;
mtd->erase = nand_erase;
mtd->point = NULL;
mtd->unpoint = NULL;
写nand flash的驱动一般都是通过nand的控制器去控制各项信号的收发的;
所以可以用nand_chip.controller来做一些控制器的操作工作
.attach_chip:nand芯片与设备驱动挂接上时要调用的函数;
.detach_chip: nand芯片与设备驱动分离时要调用的函数,nand_cleanup里面会调用;
.setup_data_interface: nand_scan的第一阶段调用:里面可以做nand控制器的时序设置已经pinctrl设置;
一般写mtd设置都要实现mtd->_erase,或者mtd->_write,mtd->_read函数,但是如果是用nand_scan向内核注册nand设备,这三个函数不用自己提供;
写nand设备驱动之前要确保内核是否支持这款nand,在nand_ids.c里面有其所支持的所有厂家的nand芯片;在nand_scan的第一阶段,要读nand芯片的
厂家ID,设备ID,根据这两个ID扫描nand_ids.c里面的结构体看是否支持;
设备驱动特别要提供的三个函数:片选函数,判断正忙函数(不需要等待,只需要返回状态位),发命令和发地址函数(这个最核心),提供读写的IO地址;
tCLS-tWP
tRC,tWC
tCLH
NFCONF,
测试驱动:
取消选中原厂的驱动
-> Device Drivers |
| -> Memory Technology Device (MTD) support (MTD [=y]) |
| (1) -> Raw/Parallel NAND Device Support (MTD_RAW_NAND [=y])
make menuconfig 取消宏CONFIG_MTD_NAND_S3C2410
内核挂接的根文件系统改为网络文件系统,因内核中去掉了原厂nand的驱动,已经无法从nand中挂接烧写在里面的驱动了;
挂接到网络文件系统之后;
insmod jaky2440_nand.ko
ls /dev/mtd*
/dev/mtd0 /dev/mtd3 /dev/mtd6 /dev/mtdblock2
/dev/mtd0ro /dev/mtd3ro /dev/mtd6ro /dev/mtdblock3
/dev/mtd1 /dev/mtd4 /dev/mtd7 /dev/mtdblock4
/dev/mtd1ro /dev/mtd4ro /dev/mtd7ro /dev/mtdblock5
/dev/mtd2 /dev/mtd5 /dev/mtdblock0 /dev/mtdblock6
/dev/mtd2ro /dev/mtd5ro /dev/mtdblock1 /dev/mtdblock7
其中/dev/mtd0~/dev/mtd7:为块设备对应的字符设备,用于烧写,擦除所用;
/dev/mtdblock0~/dev/mtdblock7:为实际的块设备,用于挂接以及读写所用;
在将块设备挂接出来之前最好先用mtd-utils工具将其整个区擦除一遍;
制作擦除工具:参考https://www.cnblogs.com/lifexy/p/7701181.html
用arm-linux-gcc-4.3.2作为编译工具(其他的编译工具编译不了);
tar xjf mtd-utils-05.07.23.tar.bz2
cd mtd-utils-05.07.23
cd utils
vi Makefile
添加CROSS=arm-linux-
make 生成flash_erase flash_eraseall
cp flash_erase flash_eraseall yangqing@10.150.50.162:/home/share/zjq_162/
擦除分区:
./flash_eraseall /dev/mtd6 (擦除是用字符设备)
挂接块设备出来:
mount -t yaffs /dev/mtdblock6 /ready_folder //以yaffs的格式将块设备挂接出来;
cd /ready_folder
vi test.txt
读写文件操作;