第三部分:根据sep4020编写具体nand驱动
sep4020是一款非常好优秀的arm720T芯片,以下介绍一下sep4020的nand驱动编写
1. sep4020的nand控制器只支持dma搬运,这也是考虑到一般对nand进行读写都是一页一页的,所以利用dma可以有效的提高效率。
DMA也带了一个比较重要的问题,DMA的源和目的需要用实地址,因为DMA不走cpu,在这点上我卡了好久,在linux中久了,什么都习惯性的填写虚地址,结果在测试的时候发现dma怎么都不工作,把所有的东西都看了又看,都没问题,郁闷的不行了,最后在张师兄的提醒下才发现DMA不能给虚地址,恍然大悟啊!
2. 但是由于直接给DMA又带来了另外一个问题,在操作系统中,所有的地址都是虚拟地址,如何得到实际物理地址?我记得在当初在内核移植的时候写过一个函数:
#define __virt_to_bus(x) __virt_to_phys(x) ,跟进去一看解释是物理地址,虚拟地址的转换,此时我认为操作系统的地址转换就是通过这个来实现的,因此在这里我直接给DMA赋了一个没用到的物理空间0x30002000,根据上式则应该对应的虚拟地址是0xc0002000。
我也这么做了,发现可以实现,但是总是会出现一些莫名其妙的错误,我和阿虚都快崩溃了,最后发现其实这种读法是不正确的,我们在进行同一页重复性读取实验中,10次中有一次不正确,排除了所有其他可能的因素,最后又怀疑到操作系统是不是真的这样将虚实地址的?操作系统可操作的地址空间应该有4G,如果都是这样联系虚实地址,那岂不是操作系统只能分配我们的SDRAM的8M空间,因此这种想法是错误的。最后我们使用了专门用来分配DMA地址的函数dma_alloc_coherent,实验一切正确,更加验证了我们的想法。
由于第一次在操作系统中使用dma,我们边做边摸索,以后绝对不在dma上再吃这么多跟头了,呵呵。
3 sep4020中所有的寄存器都是32位的,这点也是和其他芯片的nand控制所不同的,打个比方:我们需要给nand控制器的地址寄存器一个地址,我们是直接将地址赋给这个32位的寄存器,但是对于其他的芯片(比如2410),它的地址寄存器用8位,需要你串行的将地址依次输入进去,这点是需要我们注意的。
还有就是我们的nand控制器的命令寄存器比较特殊,它有个使能位,芯片手册推荐配好地址寄存器后,再配命令寄存器,同时使能,但由于尽量不变动linux的内核源码,我使用的是先按照mtd设备中通用的做法,发命令,发地址,然后在读写函数的最后一条指令将nand使能。
4. 有一点比较重要,sep4020的nand控制器只能528字节的写,而在linux中大量采用了先写512再写16,甚至有单独写16的函数,因此这里必须做改动。我们采用了先读后写的方法,比如写16字节(写16肯定是最后的00b区),我们先读出这一页的所有528字节,将前面的512取出再接上我们要写的16字节,这样凑满了528字节后写入。
为了实现在读写的速度更快,我们根据nand的特性进行分析,在写512字节的时候,那一页肯定是空白的,未被使用过的,因此它的oob区也肯定是全ff,因此在写512时我们将后面剩余的16字节自动填充ff,这样就省去了从nand读出的步骤,大大加快了nand写的速度。
5. 奇怪的write_byte
在我们调试的时候曾发现过一个问题,给nand发了一条写命令0x80,但是此时还未使能,但通过printk发现此时nand控制器一直处于忙状态,最后通过一步步的锁定,发现在给nand发了0x80给我们的com寄存器,但是此时printk这个寄存器的值竟然是0x80808080,而我们的第一位恰好是使能位,因此此时由于调用系统的write_byte,com寄存器从本应的0x00000080变成了0x80808080,导致了我们nand控制一直是忙,无法进行下面的操作。最后无奈之下重载了write_byte和read_byte,问题就解决了,很奇怪的是我也跟进去看过系统的代码,和我的一样啊,怎么到它那儿就这样了呢?现在觉得有可能的是:write_byte的寄存器一般都是8位的,而我们的寄存器是32位的,因此系统连续赋了4次,这仅仅是我的一种猜测,等待以后验证。
6. 编写中碰上的一些非常傻的问题:
*(unsigned long *)EMI_NAND_COM 和EMI_NAND_COM混淆了,还是指针学的不好。
出现一些很古怪的问题的时候,一定要记得检查你的头文件
切记保证你的make menuconfig中的选项正确
7.不能将yaffs 中的Turn off debug chunk erase check选上,不然会出现mtd ecc chunk的问题