vivi中对nandflash的操作还是比较多的,比如vivi启动时,首先要将自身的代码从nandflash里复制到sdram里,这一步就需要对nandflash的读取。此时可以通过nandflash控制器来操作,所以实现起来会简单一些。
到了vivi后面的阶段会引入mtd,用以增强对nandflash的操作(读、写、ecc较验等)。mtd封闭了一些对nandflash操作的细节,对上层操作提供统一的接口,如读、写等等。
最后,vivi引导内核时还需要正确的分区,比如bon分区和mtd分区,如果分区上出了一点错误,最终将导致内核不能正常的引导启动。而这些都与nandflash是密切相关的。可见nandflash的重要性。
单独拿nandflash出来讲,还是有点偏大。于是我决定从几个层次来认识与nandflash有关的操作。
这里我的芯片是三星的K9F1208,很通用的一款芯。
容量为:64M(67,108,864)x8bit。nandflash)(注意宽度为8位)
另外还有 2,048K(2,097,152)x8bit 即2M的额外空间。这个在下面会分析到。
大体上了解了这么多的参数就够了。
先来画一个nandflash的草图吧。下面是nandflash的一个block。
--> obb(out of bank)
/
|
|----> 256B <-----|-----> 256B <----|-> 16B <-|
+-----------------+-----------------+---------+
/ / / /|
/ / / / |
/ / / / |
/ / / / |
/ / / / +
/ / / / /|
/ / / / / +
/ / / / / /|
/ / / / / / |
/ / / / / / |
------ +-----------------+-----------------+---------+ / / |
| | | | | / / |
| | 1st half page | 2st half page | | / / |
| | =256Bytes | =256Bytes | | / / |
| +-----------------+-----------------+---------+ / +
\|/ 1page->| | | | / / -> bit0
+-----------------+-----------------+---------+ / -> bit1
32pages | | | | / -> bit2
=1Block | | | | / -> bit3
/|\ | | | | / -> bit4
| | | | | / -> bit5
| | | | | / -> bit6
| | | | | / -> bit7
------ +-----------------+-----------------+---------+ |__ 1 Bytes = bit[7:0]
|-----------> 512 Bytes <---------|
这里我们需要了解到:
这款芯片是8位的,即通过8个io口来与cpu通信(包括地址、数据、命令的传送)。
如上图所示,8个bit组成1个Byte。
有多个Byte组成Page。1Page = 528 Bytes = 512 Bytes + 16 Bytes。注意这里每一页由两部分组成。前一部分512B是我们存储数据用的,后16B是用来存储ECC校验码等额外数据的。所以我们能使用到的就只有前512B。
1Block = 32Pages 。所以1Block = 32 * (512 + 12) Bytes = 2^5 * (2^9 + 2^4) = 2^14 +2^9 Bytes
我们这款芯片总共有4096个Block。所以总容量为:
4096 * (2^14 + 2^9) = 2^12 * (2^14 + 2^9) = 2^26 + 2^11 = 64MB + 2MB
在nandflash里寻址时,我们要定位到某一Byte,必须有三个地址需要确定:
1. 确定是在哪一个Block中。即Block Address
2. 在Block中的哪一页。 即Page Address
3. 一页中,具体是哪一个Byte。看上图Byte如同一列一列的排布,所以称为 Column Address。
定位64M中某一Byte,需要26位地址。再画个图比较直观一些:
26 14 13 9 8 7 0
+-----------------+-----------------+---+-----------------+
| Block Address | Page Address | Column Address |
+-----------------+-----------------+---+-----------------+
注意到bit8了吧。由于bit[7:0]只能表示256个数,而一页的数据区有512个,所以在这块芯片里将一页分为了两个半页。当bit8=0时为第一半页,bit8=1时为第二半页。bit8是由硬件来控制的,当读完一个半页之后,bit8会自动置位。
如此一来,当我们要取nandflash的某一Byte的数据时,就需要分4步来传递地址,然后才能读取到那一字节的数据。
1. 传递bit[7:0] 。相当于传递了Column Address
2. 传递bit[16:9] 。
3. 传递bit[24:17] 。
4. 传递bit[25] 。
可以看到这四个步就是为了传递整个26位地址(bit8特殊)。这就是我们通常所说的“四步寻址”。
由于s3c2410已经集成了nandflash控制器,所以对于nandflash的操作,地址、命令、数据的传递都是通过写nandflash控制器的寄存器来完成的。对于nandflash控制器的详细说明可以看2410的数据手册。
数据手册里有以下几句话,是关于nandflash控制器的配置的。在从nandflash里读出数据时,我们也是按照这个顺序来的。
NAND FLASH MODE CONFIGURATION
1. Set NAND flash configuration by NFCONF register.
2. Write NAND flash command onto NFCMD register.
3. Write NAND flash address onto NFADDR register.
4. Read/Write data while checking NAND flash status by NFSTAT register. R/nB signal should be checked
before read operation or after program operation.
vivi最开始使用nandflash是要把vivi自身从nandflash复制到sdram里。来看一下它是怎么完成的。
buf 为目标地址。 start_addr是代码位于nandflash中的起始地址(一般为0)。size为代码的大小。
在调用这段代码之前,已经将NFCONF等都配置好了(详见head.S)。
#define NAND_SECTOR_SIZE 512
#define NAND_BLOCK_MASK (NAND_SECTOR_SIZE - 1)
int
nand_read_ll(unsigned char *buf, unsigned long start_addr, int size)
{
unsigned int i, j;
if ((start_addr & NAND_BLOCK_MASK) || (size & NAND_BLOCK_MASK))
return -1;
/* chip Enable */
NFCONF &= ~0x800; //使能芯片
for (i = 0; i < 10; i++);
for (i = start_addr; i < (start_addr + size);) {
/* */
NFCMD = 0;
/* Write Address ,这里分四步写地址,上面已经分析过了*/
NFADDR = i & 0xff;
NFADDR = (i >> 9) & 0xff;
NFADDR = (i >> 17) & 0xff;
NFADDR = (i >> 25) & 0xff;
wait_idle();//写完地址后等待,直到nandflash返回一个标志信号,表明已处理完,然后再进入下面的工作。
/* 完成了之前的工作之后,便可以从nandflash里读出数据了。读数据是直接从寄存器NFDATA中取出,然后写入目标地址 */
for (j = 0; j < NAND_SECTOR_SIZE; j++, i++) {
*buf = NFDATA & 0xff;
buf++;
}
}
/* chip Disable */
NFCONF |= 0x800; //每次在完成了对nandflash的操作之后,便将其禁止掉。
return 0;
}
|
上面这段程序很清楚。但需要注意一个地方,一次读取要读512(NAND_SECTOR_SIZE)个字节。读取的时候默认是从第一个半页开始读取的。如果要从第二个半页开始读取,我们则要先写入一个命令,来使bit8置位。读完1st half page后,bit8自动置1,读完2st half page后,bit8也会自动清0 。
同样,读取时,我们也可以不从一页的开始读取。比如,可以从一页的中间读取,这种情况我还没有分析太清楚,但上面的程序显然是必须从某一页的开始读起的。因为在for循环里会连续向buf写512个数据。推荐从页始开始读比较好。
nandflash好些个command。写入不同的command时,也就可以对其进行不同的操作。
----------------------------+--------------+----------+-----------
Function | 1st. Cycle |2nd. Cycle| 3rd. Cycle
----------------------------+--------------+----------+-----------
Read 1 | 00h/01h(1) | - | - 00:读1st half page 01:读2st half page
----------------------------+--------------+----------+-----------
Read 2 | 50h | - | -
----------------------------+--------------+----------+-----------
Read ID | 90h | - | -
----------------------------+--------------+----------+-----------
Reset | FFh | - | -
----------------------------+--------------+----------+-----------
Page Program (True) | 80h | 10h | -
----------------------------+--------------+----------+-----------
Page Program (Dummy) | 80h | 11h | -
----------------------------+--------------+----------+-----------
Copy-Back Program(True) | 00h | 8Ah | 10h
----------------------------+--------------+----------+-----------
Copy-Back Program(Dummy) | 03h | 8Ah | 11h
----------------------------+--------------+----------+-----------
Block Erase | 60h | D0h | -
----------------------------+--------------+----------+-----------
Multi-Plane Block Erase | 60h----60h | D0h | -
----------------------------+--------------+----------+-----------
Read Status | 70h | - | -
----------------------------+--------------+----------+-----------
Read Multi-Plane Status | 71h | - | -
----------------------------+--------------+----------+-----------