紧接着上一章,下面进入Bootloader第二阶段
第二阶段需要完成三项工作:
- 从NAND FLASH 将内核读取到SDRAM中;
- 设置bootloader需要传递给内核的参数;
- 跳转执行内核;
第一步:将内核从NAND FLASH 读取到SDRAM
内核是使用uboot事先就烧写到NAND FLASH的,内核在NAND FLASH中的起始地址是0X60000+64,内核大小小于2M。要完成内核转移工作,需要在bootloader阶段初始化NAND FLASH,使其具备读写功能。在进行初始化NAND FLASH之前,建议先了解下NAND FLASH 原理,参考文档《[嵌入式Linux应用开发完全手册》第八章,在此不再赘述。
(1)初始化 NAND FLASH
S3C2440有NAND FLASH 控制器,本文所述bootloader 仅仅需要读NAND功能,根据具体的NAND配置相应参数,使NAND具备读功能。初始化NAND需要关注以下参数:
TACLS:表示CLT/ALE的建立时间(setup time),由下图2 可得TACLS = tcls - twp 。表示发出CLE 或者 ALE后,需要等待多长时间才可发出WE信号。
TWRPH0:表示nWE/nRE持续的时间,读写管脚低电平需要持续的时间,对应下图 1 twp。
TWRPH1:表示写进去的数据起作用的时间(hold time),数据从管脚输入到NAND Flash 阵列数据有效的时间,对应下图1 tCLH / tALH。
图1 nand flash手册 时序
图2 S3C2440 nand flash 读时序
本文涉及的NAND型号为 K9F2G08U0M ,上述参数的值见下表:
图3 nand flash手册 参数表
由表可知TACLS:CLE AND ALE setup time >= 0ns ; TWRPH0 :R/W time >=15ns ; TWRPH1 : CLE AND ALE Hold time >=5ns ,这样便可以设置NAND 控制器寄存器相应的参数了。NAND 初始化时需要配置寄存器:NFCONF and NFCONT,下面来算具体位的值:
TACLS : Duration = HCLK x TACLS >=0 TACLS = 0;
TWRPH0 : Duration = HCLK x ( TWRPH0 + 1) >= 15ns TWRPH0 = 1;
TWRPH1 : Duration = HCLK x ( TWRPH1 + 1) >= 5ns TWRPH1 = 0;
图4 寄存器
(2)NAND FLASH 地址机制
下图是flash 示意图,一块nand flash 组成结构:
1 page = [ 2k (main ) + 64 (spare) ] B ; 一页由2 K main区 和 64B spare区组成,main 区用于存放用户数据,spare 区存放ECC校验和坏块标记的信息数据
1 block = 64 pages = ( 2k + 64 )B * 64 = (128K +4K )B
1 device = 2048 blocks = 2112 Mbi
图5
NandFlash 读取时以 页(page)位单位,以块(block)为最小单位进行擦除。因此在读取NandFlash时,指定首地址后,便开始从首地址读取,直到一页数据全部读完。
现在讲述NandFlash地址寻址方式:
NandFlash地址分为三类:
Column Address:指定在某一页的某一列,简称列地址
Page Address : 页地址
Block Address : 块地址
每一页有(2K+64)=2112Byte,2112byte 需要12bit来表示,对于2112byte系列的NAND,这2112byte被分成1st half Page Register和2nd half Page Register,各自的访问由地址指针命令来选择,A[11:0]就是所谓的column address(列地址),在进行擦除操作时不需要它,因为以块为单位擦除。64个page需要6bit来表示,占用A[17:12],即该page在块内的相对地址,也就是确定位于哪一页。A11这一位地址被用来设置2048byte的1st half page还是2nd half page,0表示1st,1表示2nd。Block的地址是由A[18:28]来表示,也就是确定位于哪一块。用A[0:28]这29位,就可以将K9F2G08U0A这256M的数据存储空间全部访问到。2的29次方是2GBit,256MByte=2GBit。
图6
(3)NAND FLASH 读操作
如下图7所示为NAND FLASH读操作时序图,程序按时序图设计即可:先发出00H -> 发地址序列 -> 发30H -> 判忙 -> 取数据。
图7 读时序图
(4)内核从NAND 复制到SDRAM
执行 nand_read(0x60000+64, (unsigned char *)0x30008000, 0x200000); //uImage = 64字节头部 + zImage(内核),本课程烧写的内核是uImage
实现内核转移工作。内核位于NAND FLAHS 地址 0x60000+64 处, 内核需要放到SDRAM 起始地址 0x30008000 处, 内核大小小于2M,取为2M 即可。
下面为nand 相关函数:
1 void nand_init(void) 2 { 3 #define TACLS 0 4 #define TWRPH0 1 5 #define TWRPH1 0 6 /* 设置时序 */ 7 NFCONF = (TACLS<<12)|(TWRPH0<<8)|(TWRPH1<<4); 8 /* 使能NAND Flash控制器, 初始化ECC, 禁止片选 */ 9 NFCONT = (1<<4)|(1<<1)|(1<<0); 10 } 11 void nand_select(void) 12 { 13 NFCONT &= ~(1<<1); 14 } 15 16 void nand_deselect(void) 17 { 18 NFCONT |= (1<<1); // bit1 0: select 1:deselect 19 } 20 21 void nand_cmd(unsigned char cmd) 22 { 23 volatile int i; 24 NFCMMD = cmd; 25 for (i = 0; i < 10; i++); 26 } 27 28 void nand_addr(unsigned int addr) 29 { 30 unsigned int col = addr % 2048; //col A0-A10 nand flash 一页2K 31 unsigned int page = addr / 2048; //page A11-A27 32 volatile int i; 33 34 NFADDR = col & 0xff; // col 表示一页中哪个地址数据 35 for (i = 0; i < 10; i++); //延时 36 NFADDR = (col >> 8) & 0xff; 37 for (i = 0; i < 10; i++); 38 39 NFADDR = page & 0xff; //page 表示是nand flash 里面的哪一页 40 for (i = 0; i < 10; i++); 41 NFADDR = (page >> 8) & 0xff; 42 for (i = 0; i < 10; i++); 43 NFADDR = (page >> 16) & 0xff; 44 for (i = 0; i < 10; i++); 45 } 46 47 void nand_wait_ready(void) 48 { 49 while (!(NFSTAT & 1)); //0 :nand busy 1: nand ready 50 } 51 52 unsigned char nand_data(void) 53 { 54 return NFDATA; 55 } 56 void nand_read(unsigned int addr, unsigned char *buf, unsigned int len) 57 { 58 int col = addr % 2048; 59 int i = 0; 60 61 /* 1. 选中 */ 62 nand_select(); 63 64 while (i < len) //读一页走一个循环 65 { 66 /* 2. 发出读命令00h */ 67 nand_cmd(0x00); 68 69 /* 3. 发出地址(分5步发出) */ 70 nand_addr(addr); 71 72 /* 4. 发出读命令30h */ 73 nand_cmd(0x30); 74 /* 5. 判断状态 */ 75 nand_wait_ready(); 76 77 /* 6. 读数据 */ 78 for (; (col < 2048) && (i < len); col++) 79 { 80 buf[i] = nand_data(); 81 i++; 82 addr++; 83 } 84 85 col = 0; //一页读完后,清零col,准备度下一页(col 从0开始) 86 } 87 88 /* 7. 取消选中 */ 89 nand_deselect(); 90 }
第二步:设置BootLoader 和 内核需要传递的启动参数
1 //设置启动参数 2 static struct tag *params; 3 4 void setup_start_tag(void) 5 { 6 params = (struct tag *)0x30000100; //内核和bootloader 约定存放参数 地址 0x30000100 ~ 0x30008000 7 8 params->hdr.tag = ATAG_CORE; //表单必须以ATAG_CORE node点开始 9 params->hdr.size = tag_size (tag_core); 10 11 params->u.core.flags = 0; 12 params->u.core.pagesize = 0; 13 params->u.core.rootdev = 0; 14 15 params = tag_next (params); //当前地址+头部大小 16 } 17 18 void setup_memory_tags(void) //内存tag根据内存连续片段的多少,可以设置多个tag 19 { 20 params->hdr.tag = ATAG_MEM; //该TAG类型存放系统的内存信息(起始地址,内存大小) 21 params->hdr.size = tag_size (tag_mem32); 22 23 params->u.mem.start = 0x30000000; //SDRAM 起始地址 24 params->u.mem.size = 64*1024*1024; //内存大小64M 25 26 params = tag_next (params); 27 } 28 29 int strlen(char *str) 30 { 31 int i = 0; 32 while (str[i]) 33 { 34 i++; 35 } 36 return i; 37 } 38 39 void strcpy(char *dest, char *src) 40 { 41 while ((*dest++ = *src++) != '\0'); 42 } 43 44 void setup_commandline_tag(char *cmdline) 45 { 46 int len = strlen(cmdline) + 1; //字符串包括结束字符,所以要+1 47 48 params->hdr.tag = ATAG_CMDLINE; //命令行TAG类型,具体命令行由形参带入 49 params->hdr.size = (sizeof (struct tag_header) + len + 3) >> 2; 50 51 strcpy (params->u.cmdline.cmdline, cmdline); 52 53 params = tag_next (params); 54 } 55 56 void setup_end_tag(void) 57 { 58 params->hdr.tag = ATAG_NONE; 59 params->hdr.size = 0; 60 }
1 /* 2. 设置参数 */ 2 puts("Set boot params\n\r"); 3 setup_start_tag(); 4 setup_memory_tags(); 5 setup_commandline_tag("noinitrd root=/dev/mtdblock3 init=/linuxrc console=ttySAC0"); 6 setup_end_tag();
第三步:跳转执行
1 /* 3. 跳转执行 */ 2 puts("Boot kernel\n\r"); 3 theKernel = (void (*)(int, int, unsigned int))0x30008000; 4 theKernel(0, 362, 0x30000100); /*ldr mov pc, #0x30008000*/ //去0x30000100地址处读取设置的tag值,传给内核 ; 362 -> 单板号