uboot移植之前期准备篇1
uboot移植之Makefile分析概述篇2
boot移植之init_sequence_f函数数组分析(番外篇)
uboot移植之源码流程分析篇3(超详细!)
uboot移植之修改支持SDRAM篇4
uboot移植之修改支持NorFlash篇5
目录
1、预备知识
2、开发文件对象2410 -> 2440
3、修改分析代码
4、遗留问题
5、遗留问题“NAND write to offset 0 failed -5”解决方法
参考《K9F2G08U0C》手册章节“Pin Description”,可知道Nand Flash各引脚的使用说明如下图:
在操作Nand的时候,只需要往特定的寄存器中写值/读值,就可以实现发地址,发数据,读数据的功能了,中间一些CLE,ALE引脚电平控制操作都通过Nand Flash控制器帮我们搞定了。如果没有Nand控制器,那么这些工作都需要我们编程完成。
①发地址 NFADDR=地址值
②发数据 NFDATA=数据值
③发命令 NFCMMD=命令值
④读数据 val=NFDATA
存储芯片的编程一般可分为这几个过程:1、初始化;2、识别;3、读;4、写;5、擦除;
而对于我们的Nand编程,则:
1、Nand芯片的Nand Flash控制器
2、读取ID
3、一次读一个页
4、一次写一个页
5、一次擦除一个块(页是块的n倍)
nand的底层程序框架可以分为两部分:一部分是通用的协议层,另一部分是和单板硬件相关。比方说:xxx函数知道发出90命令,接着再发出0地址,然后读出的第一个数据是厂家id,第二个数据是设备id。这是所有nand都有的一个特点,但是至于怎样发命令,怎样发地址等等,这些都是协议层中的代码 使用底层提供的函数 去实现(后面分析代码可以知道,其实就是构造好mtd_info结构体提供给协议层使用,里面含有各种底层函数,如果我们没有去定义新函数,那么将会使用默认函数去发命令,发地址...)。所以我们应该重点去修改,查看底层相应的功能函数,比方说:选中、发命令、发地址、发数据、读数据、判断状态。另外,由于2440和2410的寄存器存在差异性,所以,在程序中当涉及寄存器操作时候,一定要认真查看相应的手册,确定操作无误。
在drivers\mtd\nand\目录下,将s3c2410_nand.c复制为s3c2440_nand.c,并修改该目录下的Makefile
在include\configs\smdk2440.h中修改2410对应的宏为2440
uboot启动输出信息中“NAND: 0 MiB”,在工程中搜索“NAND: ”,定位common\board_r.c的initr_nand()函数,并通过两次的函数嵌套调用,最终在nand_init_chip函数确定了Nand的大小并输出。
base_addr=0x4E000000(nand的NFCONF寄存器地址)。nand的IO读写地址会在 board_nand_init 函数中重新设置。
在drivers\mtd\nand\s3c2440_nand.c的 board_nand_init 函数中
nand_reg指向Nand控制器的第一个寄存器NFCONF(0x4E000000),Nand的io读写地址是在该函数中被重新设置为NFDATA的地址。nand->select_chip所指向的片选函数为NULL,那么将会在drivers\mtd\nand\nand_base.c的函数nand_set_defaults 中被设置为使用默认函数。这是底层相关的函数,默认提供的函数可能并不符合我们的要求,后面有必要去修改它。
tacls twrph0 twrph1简单来说就是脉冲时间参数,需要配置NFCONF寄存器。当发生①②③④操作时候,就让它按照设置好的时间参数去控制Nand Flash给它发地址、发命令等等,因为我们外接的Nand Flash可能是不同种类的,所以就要按照特定的类别要求去设置好,让控制器发出符合该型号nand的时序控制信号。对比2440和2410手册我们可以知道,操作的位已经发生变化,需要重新来设置。
翻阅2440手册,找到对应的时间参数配置寄存器NFCONF,以及Nand Flash的内存定时说明图:
nWE:低电平有效。
翻阅《K9F2G08U0C》Nand手册,找到该型号nand在特定电压(3.3v)下的最小时间参数:
我们可以借助命令锁存周期说明图(Command Latch Cycle)来理解:
根据AC Timing表可以知道,t(CLS)和t(WP)的最小值为12us,所以tacls最小为0。
前面篇节已经设置HCLK=100Hz,根据该手册中指定的最小时间以及2440中的参数公式可以反推出,twrph0 twrph1的最小设置分别为:1 , 0。所以,使用程序提供的默认参数完全没有问题。
在该函数对nand控制器的初始化中并没有读写操作Nand Flash,为了避免误操作,可以取消选中。重新添加使能nand,以及初始化ECC编码/解码器,我们需要启用ecc校验 (软件生成),以保证数据安全可靠。因为Nand存在坏块缺陷,无法避免。数据写到页中,并且生成ECC校验码存放在页后紧跟的OOB区;当开始读数据的时候,先读页内容,生成所读页的ECC校验码,和原先存放在OOB区的校验码进行比较,如果匹配表明数据没有问题,否则,会通过某种方法去修改,使得数据正确。
所以,需修改drivers\mtd\nand\s3c2440_nand.c中定义的宏如下,并且将程序中的宏名字替换为2440,再注释掉137行,并模仿nfconf,配置好nfcont:
接着分析s3c24x0_hwcontrol函数,在66~71行,2410操作的是NFCONF寄存器选中/取消片选,而2440操作的是NFCONT寄存器,所以,需要修改寄存器和宏。
至此,我们通过分析观察可以知道,在 board_nand_init 函数中,主要就是构造好nand->chip结构体,设置一些底层函数,这些函数将会在nand_scan函数中排上用场。
在nand_scan调用nand_scan_ident函数,设置默认函数。然后在nand_get_flash_type函数中选中片选,复位,发出90命令,再发出0地址,接着读取id。上面也曾分析过,这是协议层函数,调用的是应该是我们底层提供给它的片选函数,默认提供的函数并不能满足我们的要求,所以有必要重新写一个片选函数。
先修改片选函数:重写一个片选函数,而不是在默认函数基础上去修改,虽然可以这样做,但是为了代码规整,不提倡。
另外,因为之前并没有设置chip->cmdfunc,所以,发命令使用的也是默认函数nand_command。
跟踪进函数nand_command,发现所谓的发命令,发地址就是调用chip->cmd_ctrl。
而chip->cmd_ctrl在函数board_nand_init就被指向了s3c24x0_hwcontrol,所以s3c24x0_hwcontrol还可以用来发命令,发地址(行地址:哪一页 列地址:页内偏移),那么该函数怎么区分是发命令?还是发地址?还是操作片选信号?
阅读nand_command函数,发现就是通过传入s3c24x0_hwcontrol的第三个参数ctrl来区分。
重现分析s3c24x0_hwcontrol是怎样实现发命令和发地址的:(原来代码)
查阅2410芯片手册,发现0x4E000008是NFADDR寄存器,0x4E000004是NFCMD寄存器。
而在2410芯片手册,发现0x4E00000C是NFADDR寄存器,0x4E000008是NFCMD寄存器。
所以有必要修改对应宏,类似上面去修改就行了,不再重复。此处仅放出修改后的代码:
同样地,查看前面代码也发现读写函数也是使用默认的提供函数。那么busw到底有没有设置呢?
一直追查,发现busw就是默认设置函数的第二个参数,而chip->options并没有被设置,所以使用的读写函数是基于8位的,符合我们的Nand Flash接口情况,所以不必去修改了。
5、总结&&结果
我们主要修改了底层的发命令,发地址,片选操作函数。在修改的过程最大限度保留了代码的编写作风,当然,你也可以直接操作寄存器达到目的。最后,一定要结合nand_command命令处理函数思考,在当发生命令调用时,代入整个流程分析,这里就不展开讨论了。另外,若要打印调试信息,可以在drivers\mtd\nand\s3c2440_nand.c开头定义:
虽然会有警告信息,但是不能忽略_DEBUG。
编译,烧写,测试(关调试):
编译,烧写,测试(开调试):
写操作有点问题,但是目前不知道问题到底出在哪了。
①开调试打印,在uboot菜单中输入:
SMDK2440 # nand erase 0 80 (没问题)
SMDK2440 # nand write 0 0 80 (输出错误信息如下)
SMDK2440 # nand write 0 0 80
NAND write: device 0 offset 0x0, size 0x80
hwcontrol(): 0xffffffff 0x81
hwcontrol(): 0xffffffff 0x80
hwcontrol(): 0xffffffff 0x80 //将参数代入hwcontrol()分析,功能是:取消选中
hwcontrol(): 0xffffffff 0x81 //选中片选
hwcontrol(): 0x70 0x83 //选中片选,发命令0x70 读状态
hwcontrol(): 0xffffffff 0x81 //选中片选
hwcontrol(): 0x80 0x83 //选中片选,发命令0x80
hwcontrol(): 0x00 0x85 //选中片选,发地址0x00
hwcontrol(): 0x00 0x05 //选中片选,发地址0x00
hwcontrol(): 0x00 0x05 //选中片选,发地址0x00
hwcontrol(): 0x00 0x05 //选中片选,发地址0x00
hwcontrol(): 0x00 0x05 //选中片选,发地址0x00
hwcontrol(): 0xffffffff 0x81
hwcontrol(): 0x10 0x83 //选中片选,发命令0x10 启动写操作 单位:1页
hwcontrol(): 0xffffffff 0x81
hwcontrol(): 0x70 0x83 //选中片选,发命令0x70 查询写操作是否完成
hwcontrol(): 0xffffffff 0x81
dev_ready //检查状态引脚
hwcontrol(): 0xffffffff 0x80
hwcontrol(): 0xffffffff 0x80
hwcontrol(): 0xffffffff 0x81
hwcontrol(): 0x00 0x83 //选中片选,发命令0x00,正好对应读操作的第一个访问周期命令
hwcontrol(): 0x00 0x85 //选中片选,发地址0x00
hwcontrol(): 0x00 0x05 //选中片选,发命令0x00
hwcontrol(): 0x00 0x05
hwcontrol(): 0x00 0x05
hwcontrol(): 0x00 0x05
hwcontrol(): 0xffffffff 0x81
hwcontrol(): 0x30 0x83 //发命令0x30 正好对应读操作的第二个访问周期命令
hwcontrol(): 0xffffffff 0x81
dev_ready
dev_ready
hwcontrol(): 0x05 0x83 //结束写操作,和读一样。
hwcontrol(): 0x828 0x85
hwcontrol(): 0x08 0x05
hwcontrol(): 0xffffffff 0x81
hwcontrol(): 0xe0 0x83
hwcontrol(): 0xffffffff 0x81
hwcontrol(): 0xffffffff 0x80
hwcontrol(): 0xffffffff 0x80
NAND write to offset 0 failed -5
0 bytes written: ERROR
写操作:(操作单位:/1页)
读操作:(操作单位:/1页)
SMDK2440 # nand read 0x30000000 0 80
②关调试打印,在uboot菜单中输入:
附:命令集表
由于nand存在位反转缺陷,当写入页数据的时候,需要根据页数据生成ECC校验码,并写入当前页后面的oob区。当开始读该页的数据的时候,由于nand的位反转缺陷,可能导致读出的数据某位发生了错误。所以需要继续读出oob区的校验码,根据校验码修正数据中的错误位。
根据输出的错误信息,在工程寻找出错位置函数,发现于cmd\nand.c的nand_write_skip_bad函数,返回值错误。
跟踪进该函数,加入打印调试信息,根据输出信息发现其实是在调用nand_verify函数验证写入数据和缓冲区的数据的过程中出错,导致写入不成功。校验码其实早在nand_write的时候就已经写入oob区了,读取数据的时候自然会根据oob区的校验码修改数据,为什么还要和缓冲区的数据校验呢?(缓冲区的数据其实就是源数据)另外,我发现在旧版的uboot中,并没有这个判断选项,所以暂且先将其关闭,测试是否能成功。(测试证明没问题)
修改前uboot输出:(添加详细注释)
SMDK2440 # nand write 0 0 800
argv[0]=nand //该参数是我添加打印的,源码没有
argv[1]=write
argv[2]=0
argv[3]=0
argv[4]=800NAND write: device 0 offset 0x0, size 0x800
in else raw size=2048,off=0,dev=0 //自行添加打印的,源码没有
after else raw size=2048,off=0,dev=0 //自行添加打印的,源码没有
before nand_write_skip_bad addr=0,off=0,rwsize=0,maxsize=2048,ret=0 //自行添加打印的,源码没有
inskip blocksize=131072,length = 2048,offset=0 //自行添加打印的,源码没有
hwcontrol(): 0xffffffff 0x81
hwcontrol(): 0xffffffff 0x80
hwcontrol(): 0xffffffff 0x80
inskip actual=0,need_skip=0 //自行添加打印的,源码没有
need_skip = 0 //自行添加打印的,源码没有
inskip before write buffer=0,length = 2048,offset=0,rval=0 //自行添加打印的,源码没有
hwcontrol(): 0xffffffff 0x81
hwcontrol(): 0x70 0x83
hwcontrol(): 0xffffffff 0x81
hwcontrol(): 0x80 0x83
hwcontrol(): 0x00 0x85
hwcontrol(): 0x00 0x05
hwcontrol(): 0x00 0x05
hwcontrol(): 0x00 0x05
hwcontrol(): 0x00 0x05
hwcontrol(): 0xffffffff 0x81
hwcontrol(): 0x10 0x83
hwcontrol(): 0xffffffff 0x81
hwcontrol(): 0x70 0x83
hwcontrol(): 0xffffffff 0x81
dev_ready
hwcontrol(): 0xffffffff 0x80
hwcontrol(): 0xffffffff 0x80
inskip after write buffer=0,length = 2048,offset=0,rval=0 //自行添加打印的,源码没有
hwcontrol(): 0xffffffff 0x81
hwcontrol(): 0x00 0x83
hwcontrol(): 0x00 0x85
hwcontrol(): 0x00 0x05
hwcontrol(): 0x00 0x05
hwcontrol(): 0x00 0x05
hwcontrol(): 0x00 0x05
hwcontrol(): 0xffffffff 0x81
hwcontrol(): 0x30 0x83
hwcontrol(): 0xffffffff 0x81
dev_ready
dev_ready
hwcontrol(): 0xffffffff 0x80
hwcontrol(): 0xffffffff 0x80
inskip after verify buffer=0,length = 2048,offset=0,rval=-5 //自行添加打印的,源码没有
NAND write to offset 0 failed -5
after nand_write_skip_bad addr=0,off=0,rwsize=0,maxsize=0,ret=0 //自行添加打印的,源码没有
0 bytes written: ERROR
修改后uboot输出:(添加详细注释)
参考资料:
《嵌入式Linux应用开发完全手册》 韦东山著
《S3C2440A_UserManual_Rev13》
《K9F2G08U0C(NAND FLASH)》