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对比的;


nand\_scan
nand\_scan\_with\_ids
nand\_scan\_ident
nand\_detect
nand\_onfi\_detect //检查是否符合ONFI标准
nand\_jedec\_detect //检查是否符合JEDEC标准


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;


if (chip->legacy.waitfunc == NULL)
chip->legacy.waitfunc = nand\_wait;

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 [email protected]:/home/share/zjq\_162/

擦除分区:
./flash\_eraseall /dev/mtd6 (擦除是用字符设备)

挂接块设备出来:
mount -t yaffs /dev/mtdblock6 /ready\_folder //以yaffs的格式将块设备挂接出来;
cd /ready\_folder
vi test.txt
读写文件操作;

标签: Linux, dev, 驱动, 芯片, chip, nand, 管脚, mtd

相关文章推荐

添加新评论,含*的栏目为必填