平台:smart210
CPU:s5pv210
目标:控制核心板上的Nand Flash,对其进行读写操作,本文为上文续篇,主要实现的是对nand flash进行读/写与块擦除操作
- void nand_init(void)
- {
- // 1. config nandflash controller
- NFCONF = (TACLS<<12)|(TWRPH0<<8)|(TWRPH1<<4)|(0<<3)|(0<<2)|(1<<1)|(0<<0);
- NFCONT = (0<<18)|(0<<17)|(0<<16)|(0<<10)|(0<<9)|(0<<8)|(0<<7)|(0<<6)|(0x3<<1)|(1<<0);
- // 2. config memory port link to nand flash
- MP0_1CON = 0x22333322;
- MP0_2CON = 0x00002222;
- MP0_3CON = 0x22222222;
- // 3. reset
- nand_reset();
- }
上文我们研究了NFCONF与NFCONT两个寄存器的设置,以及配置MP0_1,MP0_2,MP0_3的必要性,针对nand flash的初始化进行到了最后阶段,就是reset了。
可以得知Reset命令为FFh,这里#define NAND_CMD_RES 0xff
- static void nand_reset(void)
- {
- nand_select_chip(); //NFCONT bit1=0
- nand_send_cmd(NAND_CMD_RES);//NFCMMD=NAND_CMD_RES
- nand_wait_idle();//wait until NFSTAT bit4=1 means no busy
- nand_deselect_chip();//NFCONT bit1=1
- }
对于nand_reset()函数,顺序依次是片选,命令,等待,解除片选。
接下来就是获取nand flash的id,这次的思路是片选,命令,地址,等待,读取,解除片选。
- void nand_read_id(void)
- {
- nand_id_info nand_id;
- nand_select_chip();
- nand_send_cmd(NAND_CMD_READ_ID);//发送读ID命令
- nand_send_addr(0x00);
- nand_wait_idle();
- nand_id.IDm = nand_read();
- nand_id.IDd = nand_read();
- nand_id.ID3rd = nand_read();
- nand_id.ID4th = nand_read();
- nand_id.ID5th = nand_read();
- printf("nandflash: makercode = %x,devicecode = %x\r\n",nand_id.IDm,nand_id.IDd);
- nand_deselect_chip();
- }
- static void nand_send_addr(unsigned long addr)//send addr as column address and row address to send
- {
- unsigned long i;
- unsigned long col, row;
- col = addr % NAND_PAGE_SIZE; //column :the address in a page 页内地址
- row = addr / NAND_PAGE_SIZE; //row: the page address 页地址
- NFADDR = col & 0xff; // Column Address A0~A7
- for(i=0; i<10; i++);
- NFADDR = (col >> 8) & 0x0f; // Column Address A8~A11
- for(i=0; i<10; i++);
- NFADDR = row & 0xff; // Row Address A12~A19
- for(i=0; i<10; i++);
- NFADDR = (row >> 8) & 0xff; // Row Address A20~A27
- for(i=0; i<10; i++);
- NFADDR = (row >> 16) & 0xff; // Row Address A28~A30
- for(i=0; i<10; i++);
- }
获取到的id,可以根据下图判断是否正确,这块K9F1G08U0B所获取的Maker Code为0xec,Device Code为0xf1。而我们的芯片型号是K9F4G08U0B,具体细节会有所不同,我获取到的是Maker Code=0xec,Device Code=0x54。
获取id之后,证明nand flash处于正常工作状态,我们就能开始对其进行读/写以及块擦除工作了。首先是块擦除的流程图
根据流程图,操作顺序应该是 片选,块擦除命令1,块地址,块擦除命令2,等待,发读取状态命令确认块擦除完成。
之前说过了,row是用来描述页地址的,由于一个块有64页,所以特定的row值如0、64、128、192、256、320等就是块地址了。
- unsigned char nand_erase(unsigned long block_num)
- {
- unsigned long i = 0;
- unsigned long row = block_num * NAND_BLOCK_SIZE;
- nand_select_chip();
- nand_send_cmd(NAND_CMD_BLOCK_ERASE_1st);
- for(i=0; i<10; i++);
- NFADDR = row & 0xff; // Row Address A12~A19
- for(i=0; i<10; i++);
- NFADDR = (row >> 8) & 0xff;// Row Address A20~A27
- for(i=0; i<10; i++);
- NFADDR = (row >> 16) & 0xff; // Row Address A28~A30
- NFSTAT = (NFSTAT)|(1<<4);
- nand_send_cmd(NAND_CMD_BLOCK_ERASE_2st);
- for(i=0; i<10; i++);
- nand_wait_idle();
- unsigned char status = read_nand_status();
- if (status & 1 )//一旦读取到状态异常,则认为该处为坏块,需要对坏块进行一些处理。
- {
- nand_deselect_chip();
- printf("masking bad block %d\r\n", block_num);
- return -1;
- }
- else
- {
- nand_deselect_chip();
- return 0;
- }
- }
完成了块擦除,接下来就是读写了,读的前提是写,没有写入数据的nand flash 自然就没有数据给我们读,所以我们首先来进行写操作(又叫页面编程 page program),下面是写操作流程图
写操作的一开始要先明确给出页地址和页内地址作为写的起始地址,由于nand flash采取的是整页读写的策略,所以我们只需要给定一次地址,就能源源不断地往该页写数据,实际上就是把一个缓冲数组的每个元素逐个逐个地给搬移到nand flash的特定页内,由于一开始给定的地址是寻址地址而非行列地址,所以遇到字节数大于2048的缓冲数组还得做特殊处理,下面这段程序用了巧妙的方式,以检查剩余缓冲区大小为主线,同时兼顾页面的剩余大小,哪个条件先达到就处理哪一种情况。
- int copy_sdram_to_nand(unsigned char *sdram_addr, unsigned long nand_addr, unsigned long length)
- {
- unsigned long i = 0;
- nand_select_chip();
- while(length)
- {
- nand_send_cmd(NAND_CMD_WRITE_PAGE_1st);
- nand_send_addr(nand_addr);
- unsigned long col = nand_addr % NAND_PAGE_SIZE;
- i = col;
- for(; i<NAND_PAGE_SIZE && length!=0; i++,length--)
- {
- nand_write(*sdram_addr);
- sdram_addr++;
- nand_addr++;
- }
- NFSTAT = (NFSTAT)|(1<<4);
- nand_send_cmd(NAND_CMD_WRITE_PAGE_2st);
- nand_wait_idle();
- }
- unsigned char status = read_nand_status();
- if (status & 1 )
- {
- nand_deselect_chip();
- printf("copy sdram to nand fail\r\n");
- return -1;
- }
- else
- {
- nand_deselect_chip();
- return 0;
- }
- }
可以注意到,写操作命令1st和2nd携带了地址和数据,而读操作命令1st与2nd只携带了地址,接着就能顺着页面内的列地址读下去了。不管读写操作是从页面的哪个列地址开始的,最终都只能持续读写直到当前页面的最后一个字节,所以必要的时候还是需要重新计算行列地址,在新的页内内做读写操作。
- int copy_nand_to_sdram(unsigned char *sdram_addr, unsigned long nand_addr, unsigned long length)
- {
- unsigned long i = 0;
- nand_select_chip();
- while(length)
- {
- nand_send_cmd(NAND_CMD_READ_1st);
- nand_send_addr(nand_addr);
- NFSTAT = (NFSTAT)|(1<<4);
- nand_send_cmd(NAND_CMD_READ_2st);
- nand_wait_idle();
- unsigned long col = nand_addr % NAND_PAGE_SIZE;
- i = col;
- for(; i<NAND_PAGE_SIZE && length!=0; i++,length--)
- {
- *sdram_addr = nand_read();
- sdram_addr++;
- nand_addr++;
- }
- }
- unsigned char status = read_nand_status();
- if (status & 1 )
- {
- nand_deselect_chip();
- printf("copy nand to sdram fail\r\n");
- return -1;
- }
- else
- {
- nand_deselect_chip();
- return 0;
- }
- }