下面开始分析U-Boot第二阶段:
/lib_arm/board.c:
由前述分析可知,在start.S中对CPU进行了基本的设置,准备好了C程序的运行环境(设置好了堆栈并清BSS),并调用C函数start_armboot,进入lib_arm目录下的board.c文件,第236行开始即为start_armboot函数:
------- /lib_arm/board.c -------
236 void start_armboot (void)
237 {
238 init_fnc_t **init_fnc_ptr;
239 char *s;
240 #ifndef CFG_NO_FLASH
241 ulong size;
242 #endif
243 #if defined(CONFIG_VFD) || defined(CONFIG_LCD)
244 unsigned long addr;
245 #endif
- 一些变量的定义,其中init_fnc_ptr为初始化函数序列的指针。
接下来初始化全局变量gd与gd->bd的内存区域(清0),第55行的宏定义声明了gd:
------- /lib_arm/board.c -------
55 DECLARE_GLOBAL_DATA_PTR;
这个宏定义在/include/asm-arm/global_data.h中,(凡是用到gd的文件中都会引用这个宏定义):
------- /include/asm-arm/global_data.h -------
64 #define DECLARE_GLOBAL_DATA_PTR register volatile gd_t *gd asm ("r8")
- register volatile gd_t *gd asm ("r8"):声明一个寄存器变量gd,用R8寄存器来存储其指针,不占用内存,且避免编译器再将R8分配给其他变量。
第248行设置gd指针的具体地址,其中_armboot_start表示0x33F80000地址,CFG_MALLOC_LEN定义的大小为0x30000=192kB,然后调用memset函数将gd指针开始的、大小为sizeof(gd_t)的内存区域初始化为0:
------- /lib_arm/board.c -------
247 /* Pointer is writable since we allocated a register for it */
248 gd = (gd_t*)(_armboot_start - CFG_MALLOC_LEN - sizeof(gd_t));
249 /* compiler optimization barrier needed for GCC >= 3.4 */
250 __asm__ __volatile__("": : :"memory");
251
252 memset ((void*)gd, 0, sizeof (gd_t));
- __asm__:在此插入汇编语句;
- __volatile__:禁止优化,即编译器将按照此处的语句处理;
- "memory":强制编译器假设所有内存单元均被汇编指令修改,即CPU必须重新读取内存中的数据,而不是利用cache的缓存数据。
- 举个栗子(来自网络博客):
1 int a = 5, b = 6;
2 __asm__ __volatile__("": : :"memory");
3 a = b;
此时第3行的处理,编译器不会用存放b的寄存器给a赋值,而是让CPU重新读取内存中b的值,并赋给a。
第253行、254行设置bd的指针,并将其内存区域初始化为0:
------- /lib_arm/board.c -------
253 gd->bd = (bd_t*)((char*)gd - sizeof(bd_t));
254 memset (gd->bd, 0, sizeof (bd_t));
255
256 monitor_flash_len = _bss_start - _armboot_start;
接下来按照初始化函数序列的顺序,执行所有的初始化函数:
------- /lib_arm/board.c -------
258 for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) {
259 if ((*init_fnc_ptr)() != 0) {
260 hang ();
261 }
262 }
- 初始化函数正确执行的返回值应该为0,否则将调用hang()函数,输出错误信息并进入死循环暂停。
初始化函数序列包括CPU的初始化、板级初始化、中断初始化、环境变量初始化、波特率初始化、串口初始化、控制台初始化、显示U-Boot信息以及DRAM的初始化等:
------- /lib_arm/board.c -------
212 typedef int (init_fnc_t) (void);
...
216 init_fnc_t *init_sequence[] = {
217 cpu_init, /* basic cpu dependent setup */
218 board_init, /* basic board dependent setup */
219 interrupt_init, /* set up exceptions */
220 env_init, /* initialize environment */
221 init_baudrate, /* initialze baudrate settings */
222 serial_init, /* serial communications setup */
223 console_init_f, /* stage 1 init of console */
224 display_banner, /* say that we are here */
225 #if defined(CONFIG_DISPLAY_CPUINFO)
226 print_cpuinfo, /* display cpu info (and speed) */
227 #endif
228 #if defined(CONFIG_DISPLAY_BOARDINFO)
229 checkboard, /* display board info */
230 #endif
231 dram_init, /* configure available RAM banks */
232 display_dram_config,
233 NULL,
234 };
下面对初始化函数序列进行详细说明(可以先初始化波特率和串口,便于输出调试信息,这里按顺序进行分析修改)。
初始化函数序列分析:
cpu_init:
位于/cpu/arm920t/cpu.c文件中,主要用于设置中断的堆栈,在这里并没有定义CONFIG_USE_IRQ,因此实际上无作用,不需进行修改:
------- /cpu/arm920t/cpu.c -------
92 int cpu_init (void)
93 {
94 /*
95 * setup up stacks if necessary
96 */
97 #ifdef CONFIG_USE_IRQ
98 IRQ_STACK_START = _armboot_start - CFG_MALLOC_LEN - CFG_GBL_DATA_SIZE - 4;
99 FIQ_STACK_START = IRQ_STACK_START - CONFIG_STACKSIZE_IRQ;
100 #endif
101 return 0;
102 }
board_init:
位于/board/mini2440/mini2440.c文件中,主要用于CPU时钟与引脚设置:
------- /board/mini2440/mini2440.c -------
68 int board_init (void)
69 {
70 S3C24X0_CLOCK_POWER * const clk_power = S3C24X0_GetBase_CLOCK_POWER();
71 S3C24X0_GPIO * const gpio = S3C24X0_GetBase_GPIO();
S3C24X0_CLOCK_POWER:在/include/s3c24x0.h中定义,而S3C2440多一个CAMDIVN寄存器,因此在添加修改(不修改也没事):
------- /include/s3c24x0.h -------
120 /* CLOCK & POWER MANAGEMENT (see S3C2400 manual chapter 6) */
121 /* (see S3C2410 manual chapter 7) */
122 typedef struct {
123 S3C24X0_REG32 LOCKTIME;
124 S3C24X0_REG32 MPLLCON;
125 S3C24X0_REG32 UPLLCON;
126 S3C24X0_REG32 CLKCON;
127 S3C24X0_REG32 CLKSLOW;
128 S3C24X0_REG32 CLKDIVN;
129 #ifdef CONFIG_S3C2440
130 S3C24X0_REG32 CAMDIVN;
131 #endif
132 } /*__attribute__((__packed__))*/ S3C24X0_CLOCK_POWER;
...
845 /* CLOCK & POWER MANAGEMENT */
846 #define rLOCKTIME (*(volatile unsigned *)0x4C000000)
847 #define rMPLLCON (*(volatile unsigned *)0x4C000004)
848 #define rUPLLCON (*(volatile unsigned *)0x4C000008)
849 #define rCLKCON (*(volatile unsigned *)0x4C00000C)
850 #define rCLKSLOW (*(volatile unsigned *)0x4C000010)
851 #define rCLKDIVN (*(volatile unsigned *)0x4C000014)
852 #define rCAMDIVN (*(volatile unsigned *)0x4C000018)
- S3C2440还有一些寄存器与S3C2410不同,在后续遇到时将进行修改。
S3C24X0_GPIO:在/include/s3c24x0.h中定义,在前面已经添加了S3C2440的GPIO寄存器:
------- /include/s3c24x0.h -------
385 /* I/O PORT (see manual chapter 9) */
386 typedef struct {
...
470 #ifdef CONFIG_S3C2440
471 S3C24X0_REG32 GPACON;
472 S3C24X0_REG32 GPADAT;
473 S3C24X0_REG32 res1[2];
474 S3C24X0_REG32 GPBCON;
475 S3C24X0_REG32 GPBDAT;
476 S3C24X0_REG32 GPBUP;
477 S3C24X0_REG32 res2;
478 S3C24X0_REG32 GPCCON;
479 S3C24X0_REG32 GPCDAT;
480 S3C24X0_REG32 GPCUP;
481 S3C24X0_REG32 res3;
482 S3C24X0_REG32 GPDCON;
483 S3C24X0_REG32 GPDDAT;
484 S3C24X0_REG32 GPDUP;
485 S3C24X0_REG32 res4;
486 S3C24X0_REG32 GPECON;
487 S3C24X0_REG32 GPEDAT;
488 S3C24X0_REG32 GPEUP;
489 S3C24X0_REG32 res5;
490 S3C24X0_REG32 GPFCON;
491 S3C24X0_REG32 GPFDAT;
492 S3C24X0_REG32 GPFUP;
493 S3C24X0_REG32 res6;
494 S3C24X0_REG32 GPGCON;
495 S3C24X0_REG32 GPGDAT;
496 S3C24X0_REG32 GPGUP;
497 S3C24X0_REG32 res7;
498 S3C24X0_REG32 GPHCON;
499 S3C24X0_REG32 GPHDAT;
500 S3C24X0_REG32 GPHUP;
501 S3C24X0_REG32 res8;
502
503 S3C24X0_REG32 MISCCR;
504 S3C24X0_REG32 DCLKCON;
505 S3C24X0_REG32 EXTINT0;
506 S3C24X0_REG32 EXTINT1;
507 S3C24X0_REG32 EXTINT2;
508 S3C24X0_REG32 EINTFLT0;
509 S3C24X0_REG32 EINTFLT1;
510 S3C24X0_REG32 EINTFLT2;
511 S3C24X0_REG32 EINTFLT3;
512 S3C24X0_REG32 EINTMASK;
513 S3C24X0_REG32 EINTPEND;
514 S3C24X0_REG32 GSTATUS0;
515 S3C24X0_REG32 GSTATUS1;
516 S3C24X0_REG32 GSTATUS2;
517 S3C24X0_REG32 GSTATUS3;
518 S3C24X0_REG32 GSTATUS4;
519 #endif
520 } /*__attribute__((__packed__))*/ S3C24X0_GPIO;
通过设置MPLLCON和UPLLCON来改变时钟频率:
修改时钟设置宏定义如下:
------- /board/mini2440/mini2440.c -------
33 //#define FCLK_SPEED 1
34 #define FCLK_SPEED 200
...
44 #elif FCLK_SPEED==200
45 #define M_MDIV 0x5C
46 #define M_PDIV 0x1
47 #define M_SDIV 0x2
...
50 //#define USB_CLOCK 1
51 #define USB_CLOCK 48
...
61 #elif USB_CLOCK==48
62 #define U_M_MDIV 0x38
63 #define U_M_PDIV 0x2
64 #define U_M_SDIV 0x2
在board_init函数中通过时钟设置的宏定义对时钟频率进行修改:
------- /board/mini2440/mini2440.c -------
83 /* to reduce PLL lock time, adjust the LOCKTIME register */
84 clk_power->LOCKTIME = 0xFFFFFF;
85
86 /* the CLKDIVN register, FCLK:HCLK:PCLK=4:2:1 */
87 clk_power->CLKDIVN = 0x3;
88 __asm__(
89 "mrc p15, 0, r0, c1, c0, 0n"
90 "orr r0, r0, #0xC0000000n"
91 "mcr p15, 0, r0, c1, c0, 0n"
92 );
93
94 /* configure MPLL */
95 clk_power->MPLLCON = ((M_MDIV << 12) + (M_PDIV << 4) + M_SDIV);
96
97 /* some delay between MPLL and UPLL */
98 delay (4000);
99
100 /* configure UPLL */
101 clk_power->UPLLCON = ((U_M_MDIV << 12) + (U_M_PDIV << 4) + U_M_SDIV);
102
103 /* some delay between MPLL and UPLL */
104 delay (8000);
- LOCKTIME寄存器:在更改时钟频率后,FCLK时钟信号会经过一段Lock Time的时间才正式输出新频率的时钟信号,这里保持默认值即可;
- 注意设置时钟分频比后要修改CPU总线模式,这里使用汇编代码设置为CPU异步总线模式。
接下来为GPIO的设置,在这里为了简化程序,将没有用到的GPIO保持其复位后的默认值,即不进行设置,而只对将要用到的引脚进行设置,包括
- GPIOA(功能引脚);
- GPIOB(GPB5~8对应LED1-4);
- GPIOH(GPH4~5对应UART1的TXD和RXD,由于我的MINI2440UART0存在一些问题,因此采用UART1作为调试串口输出信息)
修改如下,仅设置需要用到的引脚,其他引脚设置暂时注释掉:
------- /board/mini2440/mini2440.c -------
106 /* set up the I/O ports */
107 gpio->GPACON = 0x007FFFFF; // GPIOAs are all important functional pins
108 /* gpio->GPBCON = 0x00044555;*/
109 /* gpio->GPBUP = 0x000007FF;*/
110 gpio->GPBCON = 0x00015400; // GPB5-8 LED1-4
111 /* gpio->GPCCON = 0xAAAAAAAA;*/
112 /* gpio->GPCUP = 0x0000FFFF;*/
113 /* gpio->GPDCON = 0xAAAAAAAA;*/
114 /* gpio->GPDUP = 0x0000FFFF;*/
115 /* gpio->GPECON = 0xAAAAAAAA;*/
116 /* gpio->GPEUP = 0x0000FFFF;*/
117 /* gpio->GPFCON = 0x000055AA;*/
118 /* gpio->GPFUP = 0x000000FF;*/
119 /* gpio->GPGCON = 0xFF95FFBA;*/
120 /* gpio->GPGUP = 0x0000FFFF;*/
121 /* gpio->GPHCON = 0x002AFAAA;*/
122 /* gpio->GPHUP = 0x000007FF;*/
123 gpio->GPHCON = 0x00000A00; // GPH4-TXD1 GPH5-RXD1
124 gpio->GPHUP = 0x00000030; // GPH4 GPH5 disable pull-up
接下来是开发板序号的设置,在前面已经修改好:
------- /board/mini2440/mini2440.c -------
126 /* arch number of MINI2440-Board */
127 //gd->bd->bi_arch_number = MACH_TYPE_SMDK2410;
128 gd->bd->bi_arch_number = MACH_TYPE_MINI2440;
129
130 /* adress of boot parameters */
131 gd->bd->bi_boot_params = 0x30000100;
第131行定义了boot参数的起始地址:0x30000100,即在启动Linux内核时,将会把和启动相关的参数存放到这个地址开始的内存区域。
最后使能指令缓存与数据缓存:
------- /board/mini2440/mini2440.c -------
133 icache_enable();
134 dcache_enable();
135
136 return 0;
137 }
函数具体实现在/cpu/arm920t/cpu.c文件中:
------- /cpu/arm920t/cpu.c -------
137 void icache_enable (void)
138 {
139 ulong reg;
140
141 reg = read_p15_c1 (); /* get control reg. */
142 cp_delay ();
143 write_p15_c1 (reg | C1_IC);
144 }
...
162 void dcache_enable (void)
163 {
164 ulong reg;
165
166 reg = read_p15_c1 ();
167 cp_delay ();
168 write_p15_c1 (reg | C1_DC);
169 }
与前面的分析一致,首先读取CP15的control register(Register 1)的值,然后分别将ICache与DCache的使能位置1,使能指令缓存与数据缓存。
interrupt_init:
位于/cpu/arm920t/s3c24x0/interrupt.c文件中,主要功能为设置Timer4进行10ms的定时:
------- /cpu/arm920t/s3c24x0/interrupt.c -------
57 int interrupt_init (void)
58 {
59 S3C24X0_TIMERS * const timers = S3C24X0_GetBase_TIMERS();
60
61 /* use PWM Timer 4 because it has no output */
62 /* prescaler for Timer 4 is 16 */
63 timers->TCFG0 = 0x0f00;
64 if (timer_load_val == 0)
65 {
66 /*
67 * for 10 ms clock period @ PCLK with 4 bit divider = 1/2
68 * (default) and prescaler = 16. Should be 10390
69 * @33.25MHz and 15625 @ 50 MHz
70 */
71 timer_load_val = get_PCLK()/(2 * 16 * 100);
72 }
- 第59行获取定时器的寄存器地址;
- 第63行设置定时器4为16分频,TCFG0位[15:8]设置Timer2、3、4的预分频,定时器使用PCLK时钟信号,预分频的值为prescaler+1,在前面PCLK设置为50MHz:
- 在TCFG1中位[19:16]保持默认值0b0000,因此Timer 4的分频为1/2,结合预分频的值,则定时器的计数寄存器值计算为:
PCLK/(2 * 16 * 100)
- 因此第69行计算每次计数器载入的值,定时器计数每次减一,直至0为10ms。
获取时钟周期的函数位于/cpu/arm920t/s3c24x0/speed.c文件中:
------- /cpu/arm920t/s3c24x0/speed.c -------
76 ulong get_FCLK(void)
77 {
78 return(get_PLLCLK(MPLL));
79 }
...
82 ulong get_HCLK(void)
83 {
84 S3C24X0_CLOCK_POWER * const clk_power = S3C24X0_GetBase_CLOCK_POWER();
85
86 return((clk_power->CLKDIVN & 0x2) ? get_FCLK()/2 : get_FCLK());
87 }
...
90 ulong get_PCLK(void)
91 {
92 S3C24X0_CLOCK_POWER * const clk_power = S3C24X0_GetBase_CLOCK_POWER();
93
94 return((clk_power->CLKDIVN & 0x1) ? get_HCLK()/2 : get_HCLK());
95 }
其中get_PLLCLK函数中,由于S3C2440MPLL计算公式与S3C2410不同,因此需要进行修改:
------- /cpu/arm920t/s3c24x0/speed.c -------
56 static ulong get_PLLCLK(int pllreg)
57 {
58 S3C24X0_CLOCK_POWER * const clk_power = S3C24X0_GetBase_CLOCK_POWER();
59 ulong r, m, p, s;
60
61 if (pllreg == MPLL)
62 r = clk_power->MPLLCON;
63 else if (pllreg == UPLL)
64 r = clk_power->UPLLCON;
65 else
66 hang();
67
68 m = ((r & 0xFF000) >> 12) + 8;
69 #if defined(CONFIG_S3C2440) && (pllreg == MPLL)
70 m *= 2;
71 #endif
72 p = ((r & 0x003F0) >> 4) + 2;
73 s = r & 0x3;
74
75 return((CONFIG_SYS_CLK_FREQ * m) / (p << s));
76 }
env_init:
env_init函数主要作用是初始化环境变量,通过搜索函数,可以看到如下结果,可知根据环境变量所存储的位置不同而有不同的定义,这里使用NAND Flash(其实并没有在NAND Flash中存储环境变量),因此应该执行common/env_nand.c文件中的env_init函数:
tzw@tzw-pc:~/arm/u-boot/u-boot-mini2440-1.1.6$ grep -nwr "env_init"
common/env_nowhere.c:57:int env_init(void)
common/env_dataflash.c:71:int env_init(void)
common/env_eeprom.c:78:int env_init(void)
common/env_flash.c:99:int env_init(void)
common/env_flash.c:260:int env_init(void)
common/env_nand.c:102:int env_init(void)
common/env_nvram.c:135:int env_init (void)
其实在env_init函数中根据宏定义直接使用了默认的环境变量(在/common/env_common.c文件中),将全局变量gd中环境变量的地址赋值为default_environment的起始地址:
------- common/env_nand.c -------
142 gd->env_addr = (ulong)&default_environment[0];
143 gd->env_valid = 1;
而后续的修改主要为了适配NAND Flash。
NAND Flash配置的相关修改:
在common/env_nand.c中,首先必须定义了CFG_ENV_IS_IN_NAND才会编译后续所有函数:
------- common/env_nand.c -------
34 #if defined(CFG_ENV_IS_IN_NAND) /* Environment is in Nand Flash */
而在/include/configs/mini2440.h中的定义为:
------- /include/configs/mini2440.h -------
180 #define CFG_ENV_IS_IN_FLASH 1
因此将其修改为CFG_ENV_IS_IN_NAND,并添加CFG_NO_FLASH宏定义(不使用NOR Flash):
------- /include/configs/mini2440.h -------
149 #define CFG_NO_FLASH 1
...
179 //#define CFG_ENV_IS_IN_FLASH 1
180 #define CFG_ENV_IS_IN_NAND 1
修改完成后尝试编译:
tzw@tzw-pc:~/arm/u-boot/u-boot-mini2440-1.1.6$ make distclean
tzw@tzw-pc:~/arm/u-boot/u-boot-mini2440-1.1.6$ make mini2440_config
tzw@tzw-pc:~/arm/u-boot/u-boot-mini2440-1.1.6$ make
根据报错信息一步一步进行修改(修改->编译->报错->修改->...)。
首先由报错信息可知需要定义CFG_ENV_OFFSET:
In file included from environment.c:30:0:
/home/tzw/arm/u-boot/u-boot-mini2440-1.1.6/include/environment.h:74:4: error: #error "Need to define CFG_ENV_OFFSET when using CFG_ENV_IS_IN_NAND"
# error "Need to define CFG_ENV_OFFSET when using CFG_ENV_IS_IN_NAND"
在/include/configs/mini2440.h中定义CFG_ENV_OFFSET:
------- /include/configs/mini2440.h -------
182 #define CFG_ENV_OFFSET 0x60000
再编译,报错,可知是/board/mini2440/flash.c中存在错误:
make[1]: Entering directory '/home/tzw/arm/u-boot/u-boot-mini2440-1.1.6/board/mini2440'
...
flash.c:33: error: syntax error before "flash_info"
由于这里并不使用NOR Flash,因此直接在Makefile中注释掉flash.o的编译:
------- /board/mini2440/Makefile -------
28 COBJS := lowlevel_nand.o mini2440.o # flash.o
再编译,报错,可知是/common/cmd_bootm.c文件中第93行有错误:
make[1]: Entering directory '/home/tzw/arm/u-boot/u-boot-mini2440-1.1.6/common'
...
cmd_bootm.c:93: error: syntax error before "flash_info"
查看cmd_bootm.c文件,可知是NOR Flash相关的结构体未定义,且仅在第91行有定义时才需要使用这个结构体,因此查找91行的宏定义:
------- /common/cmd_bootm.c -------
91 #if (CONFIG_COMMANDS & CFG_CMD_IMLS)
92 #include <flash.h>
93 extern flash_info_t flash_info[]; /* info for FLASH chips */
查找:
tzw@tzw-pc:~/arm/u-boot/u-boot-mini2440-1.1.6$ grep -nwr "CFG_CMD_IMLS"
...
include/cmd_confdefs.h:87:#define CFG_CMD_IMLS 0x0020000000000000ULL /* List all found images */
可知其位于/include/cmd_confdefs.h文件中,注释掉:
------- /include/cmd_confdefs.h -------
87 //#define CFG_CMD_IMLS 0x0020000000000000ULL /* List all found images */
再编译,报错,可知造/common/cmd_flash.c中第46行有错误:
make[1]: Entering directory '/home/tzw/arm/u-boot/u-boot-mini2440-1.1.6/common'
...
cmd_flash.c:46: error: syntax error before "flash_info"
其实cmd_flash.c文件中是U-Boot命令中与NOR Flash有关的操作,查看文件,可知同样是与NOR Flash相关的结构体未定义,且仅在第34行有定义时才进行编译,因此查找定义并注释:
------- /common/cmd_flash.c -------
34 #if (CONFIG_COMMANDS & CFG_CMD_FLASH)
35
36 #if (CONFIG_COMMANDS & CFG_CMD_JFFS2) && defined(CONFIG_JFFS2_CMDLINE)
37 #include <jffs2/jffs2.h>
38
39 /* parition handling routines */
40 int mtdparts_init(void);
41 int id_parse(const char *id, const char **ret_id, u8 *dev_type, u8 *dev_num);
42 int find_dev_and_part(const char *id, struct mtd_device **dev,
43 u8 *part_num, struct part_info **part);
44 #endif
45
46 extern flash_info_t flash_info[]; /* info for FLASH chips */
------- /include/cmd_confdefs.h -------
38 //#define CFG_CMD_FLASH 0x00000020ULL /* flinfo, erase, protect */
再编译,报错,可知需要定义NAND_MAX_CHIPS:
In file included from /home/tzw/arm/u-boot/u-boot-mini2440-1.1.6/include/nand.h:29,
from env_nand.c:40:
/home/tzw/arm/u-boot/u-boot-mini2440-1.1.6/include/linux/mtd/nand.h:412: error: `NAND_MAX_CHIPS' undeclared here (not in a function)
MINI2440开发板仅一块NAND Flash,因此在/include/configs/mini2440.h中定义:
------- /include/configs/mini2440.h -------
184 #define NAND_MAX_CHIPS 1
再次编译,报错,在cmd_nvedit.c文件中的do_saveenv函数中调用了未定义的saveenv函数:
common/libcommon.a(cmd_nvedit.o)(.text+0x8a0): In function `do_saveenv':
/home/tzw/arm/u-boot/u-boot-mini2440-1.1.6/common/cmd_nvedit.c:549: undefined reference to `saveenv'
do_saveenv形式的函数为U-Boot命令所调用的函数,在/common/cmd_nvedit.c中,需要有相应的U-Boot命令宏定义:
------- /common/cmd_nvedit.c -------
538 #if defined(CFG_ENV_IS_IN_NVRAM) || defined(CFG_ENV_IS_IN_EEPROM) ||
539 ((CONFIG_COMMANDS & (CFG_CMD_ENV|CFG_CMD_FLASH)) ==
540 (CFG_CMD_ENV|CFG_CMD_FLASH)) ||
541 ((CONFIG_COMMANDS & (CFG_CMD_ENV|CFG_CMD_NAND)) ==
542 (CFG_CMD_ENV|CFG_CMD_NAND))
534 int do_saveenv (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[])
544 {
545 extern char * env_name_spec;
546
547 printf ("Saving Environment to %s...n", env_name_spec);
548
549 return (saveenv() ? 1 : 0);
550 }
551
552
553 #endif
可知需要定义CFG_CMD_ENV或者CFG_CMD_NAND,因此在/include/configs/mini2440.h中添加CFG_CMD_NAND的定义:
------- /include/configs/mini2440.h -------
79 #define CONFIG_COMMANDS
79 (CONFIG_CMD_DFL |
80 CFG_CMD_CACHE |
81 CFG_CMD_NAND |
82 /*CFG_CMD_EEPROM |*/
83 /*CFG_CMD_I2C |*/
84 /*CFG_CMD_USB |*/
85 CFG_CMD_REGINFO |
86 CFG_CMD_DATE |
87 CFG_CMD_ELF)
再次编译,报错,可知在/drivers/nand/nand.c中需要定义CFG_MAX_NAND_DEVICE和CFG_NAND_BASE:
nand.c:35: error: `CFG_MAX_NAND_DEVICE' undeclared here (not in a function)
nand.c:38: error: `CFG_NAND_BASE' undeclared here (not in a function)
由于MINI2440开发板仅有一块NAND Flash,且NAND Flash起始地址为0x00000000,因此在/include/configs/mini2440.h中声明这两个宏定义:
------- /include/configs/mini2440.h -------
185 #define CFG_MAX_NAND_DEVICE 1
186 #define CFG_NAND_BASE 0x0
再次编译,报错,根据错误信息可知缺少board_nand_init函数:
drivers/nand/libnand.a(nand.o)(.text+0x24): In function `nand_init':
/home/tzw/arm/u-boot/u-boot-mini2440-1.1.6/drivers/nand/nand.c:50: undefined reference to `board_nand_init'
这个函数为板级的NAND Flash初始化函数。
在/drivers/nand/nand.c中第65行的nand_init函数里调用了nand_init_chip函数:
------- /drivers/nand/nand.c -------
60 void nand_init(void)
61 {
62 int i;
63 unsigned int size = 0;
64 for (i = 0; i < CFG_MAX_NAND_DEVICE; i++) {
65 nand_init_chip(&nand_info[i], &nand_chip[i], base_address[i]);
66 size += nand_info[i].size;
67 if (nand_curr_device == -1)
68 nand_curr_device = i;
69 }
70 printf("%lu MiBn", size / (1024 * 1024));
71
72 #ifdef CFG_NAND_SELECT_DEVICE
73 /*
74 * Select the chip in the board/cpu specific driver
75 */
76 board_nand_select_device(nand_info[nand_curr_device].priv, nand_curr_device);
77 #endif
78 }
而在nand_init_chip函数中调用了board_nand_init函数:
------- /drivers/nand/nand.c -------
44 static void nand_init_chip(struct mtd_info *mtd, struct nand_chip *nand,
45 ulong base_addr)
46 {
47 mtd->priv = nand;
48
49 nand->IO_ADDR_R = nand->IO_ADDR_W = (void __iomem *)base_addr;
50 board_nand_init(nand);
51
52 if (nand_scan(mtd, 1) == 0) {
53 if (!mtd->name)
54 mtd->name = (char *)default_nand_name;
55 } else
56 mtd->name = NULL;
57
58 }
在这里可以先创建s3c2440_nand.c文件,并定义一个空的函数void board_nand_init(struct nand_chip *nand),然后在/drivers/nand/Makefile中添加,编译通过即可,等后续需要使用nand_init函数时再详细分析其内容:
tzw@tzw-pc:~/arm/u-boot/u-boot-mini2440-1.1.6$ cd drivers/nand
tzw@tzw-pc:~/arm/u-boot/u-boot-mini2440-1.1.6/drivers/nand$ touch s3c2440_nand.c
------- /drivers/nand/s3c2440_nand.c -------
1 void board_nand_init(struct nand_chip *nand)
2 {
3 return 0;
4 }
------- /drivers/nand/Makefile -------
28 COBJS := s3c2440_nand.o nand.o nand_base.o nand_ids.o nand_ecc.o nand_bbt.o nand_util.o
init_baudrate:
init_baudrate函数用于初始化波特率,位于/lib_arm/board.c文件中:
------- /lib_arm/board.c -------
122 static int init_baudrate (void)
123 {
124 char tmp[64]; /* long enough for environment variables */
125 int i = getenv_r ("baudrate", tmp, sizeof (tmp));
126 gd->bd->bi_baudrate = gd->baudrate = (i > 0)
127 ? (int) simple_strtoul (tmp, NULL, 10)
128 : CONFIG_BAUDRATE;
129
130 return (0);
131 }
- 调用getenv_r函数获取环境变量中"baudrate"的值,在default_environment中,baudrate默认为CONFIG_BAUDRATE的值:
------- /common/env_common.c -------
84 #if defined(CONFIG_BAUDRATE) && (CONFIG_BAUDRATE >= 0)
85 "baudrate=" MK_STR(CONFIG_BAUDRATE) "0"
86 #endif
- CONFIG_BAUDRATE在/include/configs/mini2440.h中定义:
------- /include/configs/mini2440.h -------
74 #define CONFIG_BAUDRATE 115200
因此init_baudrate函数设置波特率为115200。
serial_init:
serial_init函数位于/cpu/arm920t/serial.c文件中,其中调用serial_setbrg函数进行串口的初始化:
------- /cpu/arm920t/serial.c -------
85 int serial_init (void)
86 {
87 serial_setbrg ();
88
89 return (0);
90 }
serial_setbrg函数如下:
------- /cpu/arm920t/serial.c -------
53 void serial_setbrg (void)
54 {
55 S3C24X0_UART * const uart = S3C24X0_GetBase_UART(UART_NR);
56 int i;
57 unsigned int reg = 0;
58
59 /* value is calculated so : (int)(PCLK/16./baudrate) -1 */
60 reg = get_PCLK() / (16 * gd->baudrate) - 1;
61
62 /* FIFO enable, Tx/Rx FIFO clear */
63 uart->UFCON = 0x07;
64 uart->UMCON = 0x0;
65 /* Normal,No parity,1 stop,8 bit */
66 uart->ULCON = 0x3;
67 /*
68 * tx=level,rx=edge,disable timeout int.,enable rx error int.,
69 * normal,interrupt or polling
70 */
71 uart->UCON = 0x245;
72 uart->UBRDIV = reg;
73
74 #ifdef CONFIG_HWFLOW
75 uart->UMCON = 0x1; /* RTS up */
76 #endif
77 for (i = 0; i < 100; i++);
78 }
- 第55行获取所使用的UART的寄存器地址结构体,在这里选用的是UART1(SERIAL2):
------- /include/configs/mini2440.h -------
63 #define CONFIG_SERIAL2 1 /* we use SERIAL 2 on MINI2440 */
- 第60行计算波特率分频寄存器的值,这里使用的是PCLK时钟信号,计算公式如下:
- 第63行对UART控制寄存器进行设置:
- 第64行对UART MODEM寄存器进行设置:
- 第66行对UART line控制寄存器进行设置,无校验,1位停止位,8位数据位:
- 第71行对控制寄存器UCON进行设置,中断或轮询模式;
- 第72行对波特率分频寄存器进行设置,至此,UART设置完成,可以在UART1上看到输出的信息了(可以调用printf函数试一试)。
console_init_f:
console_init_f函数位于/common/console.c文件中仅仅是将gd结构体中的标志位置1(第364行),没有其他操作:
------- /common/console.c -------
362 int console_init_f (void)
363 {
364 gd->have_console = 1;
365
366 #ifdef CONFIG_SILENT_CONSOLE
367 if (getenv("silent") != NULL)
368 gd->flags |= GD_FLG_SILENT;
369 #endif
370
371 return (0);
372 }
display_banner:
display_banner函数位于/lib_arm/board.c文件中,作用是输出U-Boot的版本字符串:
------- /lib_arm/board.c -------
72 const char version_string[] =
73 U_BOOT_VERSION" (" __DATE__ " - " __TIME__ ")"CONFIG_IDENT_STRING;
...
133 static int display_banner (void)
134 {
135 printf ("nn%snn", version_string);
136 debug ("U-Boot code: %08lX -> %08lX BSS: -> %08lXn",
137 _armboot_start, _bss_start, _bss_end);
138 #ifdef CONFIG_MODEM_SUPPORT
139 debug ("Modem Support enabledn");
140 #endif
141 #ifdef CONFIG_USE_IRQ
142 debug ("IRQ Stack: %08lxn", IRQ_STACK_START);
143 debug ("FIQ Stack: %08lxn", FIQ_STACK_START);
144 #endif
145
146 return (0);
147 }
其实在serial_init函数执行之后就可以看到输出的信息了。
接下来由于没有定义CONFIG_DISPLAY_CPUINFO和CONFIG_DISPLAY_BOARDINFO,因此跳过print_cpuinfo函数与checkboard函数(感兴趣的话可以仿照其他开发板的函数写一个)。
dram_init:
dram_init函数位于/board/mini2440/mini2440.c文件中,将SDRAM的起始地址与大小赋值给gd结构体中相应的变量:
------- /board/mini2440/mini2440.c -------
139 int dram_init (void)
140 {
141 gd->bd->bi_dram[0].start = PHYS_SDRAM_1;
142 gd->bd->bi_dram[0].size = PHYS_SDRAM_1_SIZE;
143
144 return 0;
145 }
其定义在/include/configs/mini2440.h中,可知SDRAM起始地址为0x30000000,大小为0x4000000,与开发板上SDRAM设置一致:
------- /include/configs/mini2440.h -------
146 #define PHYS_SDRAM_1 0x30000000 /* SDRAM Bank #1 */
147 #define PHYS_SDRAM_1_SIZE 0x04000000 /* 64 MB */
display_dram_config:
display_dram_config函数位于/lib_arm/board.c文件中,输出SDRAM相关信息(SDRAM大小):
------- /lib_arm/board.c -------
156 static int display_dram_config (void)
157 {
158 int i;
159
160 #ifdef DEBUG
161 puts ("RAM Configuration:n");
162
163 for(i=0; i<CONFIG_NR_DRAM_BANKS; i++) {
164 printf ("Bank #%d: %08lx ", i, gd->bd->bi_dram[i].start);
165 print_size (gd->bd->bi_dram[i].size, "n");
166 }
167 #else
168 ulong size = 0;
169
170 for (i=0; i<CONFIG_NR_DRAM_BANKS; i++) {
171 size += gd->bd->bi_dram[i].size;
172 }
173 puts("DRAM: ");
174 print_size(size, "n");
175 #endif
176
177 return (0);
178 }
至此,初始化函数序列分析完毕。在其中,设置了开发板的时钟、用到的引脚,U-Boot的默认环境变量,波特率及串口通信,并输出了U-Boot版本信息和SDRAM的相关信息。
接下来,由于定义了CFG_NO_FLASH,因此NOR Flash相关的设置被跳过:
------- /lib_arm/board.c -------
264 #ifndef CFG_NO_FLASH
265 /* configure available FLASH banks */
266 size = flash_init ();
267 display_flash_config (size);
268 #endif /* CFG_NO_FLASH */
由于未定义CONFIG_VFD以及CONFIG_LED,这两部分的设置也被跳过。
接下来调用mem_malloc_init函数将CFG_MALLOC_LEN代表的区域清0(mem_malloc_init函数位于/lib_arm/board.c文件中,通过调用memset函数进行清0):
------- /lib_arm/board.c -------
297 mem_malloc_init (_armboot_start - CFG_MALLOC_LEN);
之前在/include/configs/mini2440.h中定义了CONFIG_COMMAND以及CFG_CMD_NAND,因此下面进行NAND Flash的初始化:
299 #if (CONFIG_COMMANDS & CFG_CMD_NAND)
300 puts ("NAND: ");
301 nand_init(); /* go init the NAND */
302 #endif
nand_init:
在之前提到过nand_init函数调用nand_init_chip函数后,进一步又调用了board_nand_init函数,当时使用了空的函数用于编译。其实在以后版本的U-Boot中给出了NAND Flash相关的底层操作函数,这里参考u-boot-2009.11/drivers/mtd/nand/s3c2410_nand.c编写用于S3C2440的NAND Flash操作函数,在/drivers/nand目录下新建s3c2440_nand.c文件:
tzw@tzw-pc:~/arm/u-boot/u-boot-mini2440-1.1.6$ cd drivers/nand
tzw@tzw-pc:~/arm/u-boot/u-boot-mini2440-1.1.6/drivers/nand$ touch s3c2440_nand.c
相关的函数说明在U-Boot第一阶段的NAND Flash拷贝中已经提到过,这里不再重复说明,代码如下:
------- /drivers/nand/s3c2440_nand.c -------
1 /*
2 * Modified from u-boot-2009.11/drivers/mtd/nand/s3c2410_nand.c
3 *
4 * Author: TZW
5 *
6 * Date: 2019-04-28
7 *
8 */
9
10 #include <common.h>
11
12 #if (CONFIG_COMMANDS & CFG_CMD_NAND)
13 # if !defined(CFG_NAND_LEGACY)
14
15 #include <nand.h>
16 #include <s3c2440.h>
17 #include <s3c24x0.h>
18 #include <asm/io.h>
19
20 #define S3C2440_NFCONF_TACLS(x) ((x)<<12)
21 #define S3C2440_NFCONF_TWRPH0(x) ((x)<<8)
22 #define S3C2440_NFCONF_TWRPH1(x) ((x)<<4)
23 #define S3C2440_NFCONF_ADVFLASH (1<<3)
24 #define S3C2440_NFCONF_2048BYTE (1<<2)
25 #define S3C2440_NFCONF_ADDRCYCLE (1<<1)
26 #define S3C2440_NFCONF_8BIT (0<<0)
27
28 #define S3C2440_NFCONT_INITECC (1<<4)
29 #define S3C2440_NFCONT_nFCE (1<<1)
30 #define S3C2440_NFCONT_EN (1<<0)
31
32 #define NAND_CMD_NONE -1
33
34 void s3c2440_addr_single_cycle(unsigned char addr)
35 {
36 S3C2440_NAND * const nand = S3C2440_GetBase_NAND();
37 writeb(addr, &nand->NFADDR);
38 }
39
40 static void s3c2440_hwcontrol(struct mtd_info *mtd, int cmd, unsigned int ctrl)
41 {
42 struct nand_chip *chip = mtd->priv;
43 S3C2440_NAND * const nand = S3C2440_GetBase_NAND();
44 ctrl = 0;
45
46 debugX(1, "hwcontrol(): 0x%02x 0x%02xn", cmd, ctrl);
47
48 if (cmd != NAND_CMD_NONE)
49 {
50 switch (cmd)
51 {
52 case NAND_CTL_CLRNCE: nand->NFCONT |= (1<<1); break;
53 case NAND_CTL_SETNCE: nand->NFCONT &= ~(1<<1); break;
54 default:
55 //writeb(cmd, chip->IO_ADDR_W);
56 writeb(cmd, &nand->NFCMD);
57 break;
58 }
59 }
60 }
61
62 static int s3c2440_dev_ready(struct mtd_info *mtd)
63 {
64 S3C2440_NAND * const nand = S3C2440_GetBase_NAND();
65 debugX(1, "dev_readyn");
66 return readl(&nand->NFSTAT) & 0x01;
67 }
68
69 void board_nand_init(struct nand_chip *nand)
70 {
71 u_int32_t cfg, ctr;
72 u_int8_t tacls, twrph0, twrph1;
73 S3C24X0_CLOCK_POWER * const clk_power = S3C24X0_GetBase_CLOCK_POWER();
74 S3C2440_NAND * const nand_reg = S3C2440_GetBase_NAND();
75
76 debugX(1, "board_nand_init()n");
77
78 writel(readl(&clk_power->CLKCON) | (1<<4), &clk_power->CLKCON);
79
80 /* initialize hardware */
81 twrph0 = 2;
82 twrph1 = 0;
83 tacls = 0;
84
85 cfg = S3C2440_NFCONF_TACLS(tacls);
86 cfg |= S3C2440_NFCONF_TWRPH0(twrph0);
87 cfg |= S3C2440_NFCONF_TWRPH1(twrph1);
88 writel(cfg, &nand_reg->NFCONF);
89
90 cfg = S3C2440_NFCONT_INITECC;
91 cfg |= S3C2440_NFCONT_nFCE;
92 cfg |= S3C2440_NFCONT_EN;
93 writel(cfg, &nand_reg->NFCONT);
94
95 /* initialize nand_chip data structure */
96 nand->IO_ADDR_R = nand->IO_ADDR_W = (void *)&nand_reg->NFDATA;
97
98 /* read_buf and write_buf are default */
99 /* read_byte and write_byte are default */
100
101 /* hwcontrol always must be implemented */
102 nand->hwcontrol = s3c2440_hwcontrol;
103
104 nand->dev_ready = s3c2440_dev_ready;
105
106 nand->eccmode = NAND_ECC_SOFT;
107
108 nand->options = 0;
109
110 debugX(1, "end of nand_initn");
111
112 return 0;
113 }
114
115 # else
116 #error "U-Boot legacy NAND support not available for MINI2440."
117 # endif
118 #endif
- mtd_info结构体位于/include/linux/mtd/mtd.h文件中(Memory Technology Device),包含存储设备的设备信息描述以及相关的操作函数等;
- nand_chip结构体位于/include/linux/mtd/nand.h文件中,包含NAND Flash的相关信息描述以及操作函数等;
- nand->option表示NAND Flash的数据宽度,0-8bit。
然后在nand_init_chip函数中第52行调用nand_scan函数读取NAND Flash相关的信息,nand_scan函数位于/drivers/nand/nand_base.c文件中,注意第2281行代码为修改后的:
------- /drivers/nand/nand_base.c -------
2267 int nand_scan (struct mtd_info *mtd, int maxchips)
2268 {
2269 int i, j, nand_maf_id, nand_dev_id, busw;
2270 struct nand_chip *this = mtd->priv;
2271
2272 /* Get buswidth to select the correct functions*/
2273 busw = this->options & NAND_BUSWIDTH_16;
2274
2275 /* check for proper chip_delay setup, set 20us if not */
2276 if (!this->chip_delay)
2277 this->chip_delay = 20;
2278
2279 /* check, if a user supplied command function given */
2280 if (this->cmdfunc == NULL)
2281 this->cmdfunc = nand_command_lp;
2282
2283 /* check, if a user supplied wait function given */
2284 if (this->waitfunc == NULL)
2285 this->waitfunc = nand_wait;
- mtd->priv中保存了一个nand_chip结构体的地址,因此这里的this就是当前操作的NAND Flash;
- busw:NAND Flash数据宽度,为8bit;
- this->chip_delay用于某些操作的延时等待;
- this->cmdfunc是NAND Flash的命令与地址函数,MINI2440开发板所用NAND Flash为大页即每页2048字节,因此对应的函数应该为nand_command_lp;
- this->waitfunc为计时等待NAND Flash空闲的函数,在计时内NAND Flash没有空闲则会打印超时信息。
接下来设置好对应的NAND Flash基本操作函数:
------- /drivers/nand/nand_base.c -------
2287 if (!this->select_chip)
2288 this->select_chip = nand_select_chip;
2289 if (!this->write_byte)
2290 this->write_byte = busw ? nand_write_byte16 : nand_write_byte;
2291 if (!this->read_byte)
2292 this->read_byte = busw ? nand_read_byte16 : nand_read_byte;
2293 if (!this->write_word)
2294 this->write_word = nand_write_word;
2295 if (!this->read_word)
2296 this->read_word = nand_read_word;
2297 if (!this->block_bad)
2298 this->block_bad = nand_block_bad;
2299 if (!this->block_markbad)
2300 this->block_markbad = nand_default_block_markbad;
2301 if (!this->write_buf)
2302 this->write_buf = busw ? nand_write_buf16 : nand_write_buf;
2303 if (!this->read_buf)
2304 this->read_buf = busw ? nand_read_buf16 : nand_read_buf;
2305 if (!this->verify_buf)
2306 this->verify_buf = busw ? nand_verify_buf16 : nand_verify_buf;
2307 if (!this->scan_bbt)
2308 this->scan_bbt = nand_default_bbt;
然后准备读取NAND Flash的设备ID进行识别:
首先通过设置S3C2440寄存器NFCONT的Reg_nCE来选中NAND Flash:
------- /drivers/nand/nand_base.c -------
2310 /* Select the device */
2311 this->select_chip(mtd, 0);
this->select_chip(mtd, 0)调用nand_select_chip函数,并最终调用s3c2440_hwcontrol函数将NFCONT寄存器的Reg_nCE(位[1])清零来选中使能NAND Flash:
------- /drivers/nand/nand_base.c -------
285 static void nand_select_chip(struct mtd_info *mtd, int chip)
286 {
287 struct nand_chip *this = mtd->priv;
288 switch(chip) {
289 case -1:
290 this->hwcontrol(mtd, NAND_CTL_CLRNCE);
291 break;
292 case 0:
293 this->hwcontrol(mtd, NAND_CTL_SETNCE);
294 break;
295
296 default:
297 BUG();
298 }
299 }
然后调用命令地址函数发送设备ID读取命令(90h)以及一个周期的地址(00h):
------- /drivers/nand/nand_base.c -------
2313 /* Send the command for reading device ID */
2314 this->cmdfunc (mtd, NAND_CMD_READID, 0x00, -1);
K9F2G08U0C NAND Flash读取ID操作如下:
- 读取时首先读到ECh,表示制造商为Samsung;
- DAh表示为设备编码;
- 第三个周期与第四个周期的数据用于表示NAND Flash的具体信息。
this->cmdfunc(mtd, NAND_CMD_READID, 0x00, -1)调用nand_command_lp函数,此函数用于发送NAND Flash的命令与地址;在S3C2440中,通过向NFCMD寄存器与NFADDR寄存器写入命令与地址即可,无需对地址/命令等锁存信号进行管理,CPU芯片内部的NAND Flash控制器会自行输出相关的锁存信号,因此在nand_command_lp中所有关于锁存信号的语句都应该注释掉。下面对nand_command_lp函数进行修改:
首先获取nand_chip结构体指针,并判断是否读取OOB(Out Of Block):
------- /drivers/nand/nand_base.c -------
642 static void nand_command_lp (struct mtd_info *mtd, unsigned command, int column, int page_addr)
643 {
644 register struct nand_chip *this = mtd->priv;
645
646 /* Emulate NAND_CMD_READOOB */
647 if (command == NAND_CMD_READOOB) {
648 column += mtd->oobblock;
649 command = NAND_CMD_READ0;
650 }
然后控制发出命令锁存信号并发送传入的命令(command);S3C2440不需用户管理锁存信号,因此将有关锁存信号的代码注释掉,直接发送命令即可:
------- /drivers/nand/nand_base.c -------
653 /* Begin command latch cycle */
654 //this->hwcontrol(mtd, NAND_CTL_SETCLE);
655 /* Write out the command to the device. */
656 this->hwcontrol(mtd, command);
657 /* End command latch cycle */
658 //this->hwcontrol(mtd, NAND_CTL_CLRCLE);
然后根据传入的列地址(column)与页地址(page_addr),发送地址,同样将所有有关锁存信号的代码注释掉;并且,S3C2440的NAND Flash控制器通过NFADDR寄存器发送地址,因此修改地址发送的代码,通过调用s3c2440_addr_single_cycle将单个地址周期发送出去:
------- /drivers/nand/nand_base.c -------
660 if (column != -1 || page_addr != -1) {
661 //this->hwcontrol(mtd, NAND_CTL_SETALE);
662
663 /* Serially input address */
664 if (column != -1) {
665 /* Adjust columns for 16 bit buswidth */
666 if (this->options & NAND_BUSWIDTH_16)
667 column >>= 1;
668 //this->write_byte(mtd, column & 0xff);
669 //this->write_byte(mtd, column >> 8);
670 s3c2440_addr_single_cycle(column & 0xff);
671 s3c2440_addr_single_cycle(column >> 8);
672 }
673 if (page_addr != -1) {
674 //this->write_byte(mtd, (unsigned char) (page_addr & 0xff));
675 //this->write_byte(mtd, (unsigned char) ((page_addr >> 8) & 0xff));
676 s3c2440_addr_single_cycle((unsigned char) (page_addr & 0xff));
677 s3c2440_addr_single_cycle((unsigned char) ((page_addr >> 8) & 0xff));
678 /* One more address cycle for devices > 128MiB */
679 if (this->chipsize > (128 << 20))
680 //this->write_byte(mtd, (unsigned char) ((page_addr >> 16) & 0xff));
681 s3c2440_addr_single_cycle((unsigned char) ((page_addr >> 16) & 0xff));
682 }
683 /* Latch in address */
684 //this->hwcontrol(mtd, NAND_CTL_CLRALE);
685 }
然后判断发送的命令是否存在其他操作,这里在读取NAND Flash设备ID时发送的是NAND_CMD_READID命令(90h),因此无需进一步操作;但当发送的命令使NAND_CMD_READ0(00h,读取一页的数据)时,需要在发送完5个地址周期后再发送开始读取命令NAND_CMD_READSTART(30h):
------- /drivers/nand/nand_base.c -------
691 switch (command) {
692
693 case NAND_CMD_CACHEDPROG:
694 case NAND_CMD_PAGEPROG:
695 case NAND_CMD_ERASE1:
696 case NAND_CMD_ERASE2:
697 case NAND_CMD_SEQIN:
698 case NAND_CMD_STATUS:
699 return;
700
701
702 case NAND_CMD_RESET:
703 if (this->dev_ready)
704 break;
705 udelay(this->chip_delay);
706 this->hwcontrol(mtd, NAND_CTL_SETCLE);
707 this->write_byte(mtd, NAND_CMD_STATUS);
708 this->hwcontrol(mtd, NAND_CTL_CLRCLE);
709 while ( !(this->read_byte(mtd) & 0x40));
710 return;
711
712 case NAND_CMD_READ0:
713 /* Begin command latch cycle */
714 //this->hwcontrol(mtd, NAND_CTL_SETCLE);
715 /* Write out the start read command */
716 this->hwcontrol(mtd, NAND_CMD_READSTART);
717 /* End command latch cycle */
718 //this->hwcontrol(mtd, NAND_CTL_CLRCLE);
719 /* Fall through into ready check */
720
721 /* This applies to read commands */
722 default:
723 /*
724 * If we don't have access to the busy pin, we apply the given
725 * command delay
726 */
727 if (!this->dev_ready) {
728 udelay (this->chip_delay);
729 return;
730 }
731 }
最后等待NAND Flash空闲,说明命令和地址已经接收,可以开始下一步(读取或其他)操作:
------- /drivers/nand/nand_base.c -------
735 ndelay (100);
736 /* wait until command is processed */
737 while (!this->dev_ready(mtd));
738 }
至此,nand_command_lp函数分析修改完成,下面回到nand_scan函数中。
在this->cmdfunc函数执行之后,向NAND Flash发送了设备ID读取命令,下面对返回的数据进行接收:
------- /drivers/nand/nand_base.c -------
2321 /* Read manufacturer and device IDs */
2322 nand_maf_id = this->read_byte(mtd);
2323 nand_dev_id = this->read_byte(mtd);
- 由K9F2G08U0C数据手册可知,读取设备ID返回的第一个字节为ECh,代表制造商,第二个字节为DAh,表示设备编码;
- this->read_byte函数即为nand_read_byte函数,每次读取一个字节;
- nand_read_byte函数通过调用readb函数读取指定地址的一个字节的数据,readb等读写函数位于/include/asm-arm/io.h文件中:
------- /drivers/nand/nand_base.c -------
205 static u_char nand_read_byte(struct mtd_info *mtd)
206 {
207 struct nand_chip *this = mtd->priv;
208 return readb(this->IO_ADDR_R);
209 }
下面通过将读出的设备编码nand_dev_id与现有的设备编码nand_flash_ids进行比较来寻找对应的NAND Flash:
------- /drivers/nand/nand_base.c -------
2326 for (i = 0; nand_flash_ids[i].name != NULL; i++) {
2327
2328 if (nand_dev_id != nand_flash_ids[i].id)
2329 continue;
nand_flash_ids定义在/drivers/nand/nand_ids.c文件中:
------- /drivers/nand/nand_ids.c -------
31 struct nand_flash_dev nand_flash_ids[] = {
...
83 {"NAND 256MiB 3,3V 8-bit", 0xDA, 0, 256, 0, NAND_SAMSUNG_LP_OPTIONS | NAND_NO_AUTOINCR},
其结构体的定义在/include/linux/mtd/nand.h文件中:
------- /include/linux/mtd/nand.h -------
365 struct nand_flash_dev {
366 char *name;
367 int id;
368 unsigned long pagesize;
369 unsigned long chipsize;
370 unsigned long erasesize;
371 unsigned long options;
372 };
找到对应类型的NAND Flash后,设置其名称(NAND 256MiB 3,3V 8-bit)与容量(256MiB);在nand_flash_ids中并没有设置页的大小,因此根据读取到的第四个数据获取NAND Flash的设置:
------- /drivers/nand/nand_base.c -------
2331 if (!mtd->name) mtd->name = nand_flash_ids[i].name;
2332 this->chipsize = nand_flash_ids[i].chipsize << 20;
...
2335 if (!nand_flash_ids[i].pagesize) {
2336 int extid;
2337 /* The 3rd id byte contains non relevant data ATM */
2338 extid = this->read_byte(mtd);
2339 /* The 4th id byte is the important one */
2340 extid = this->read_byte(mtd);
2341 /* Calc pagesize */
2342 mtd->oobblock = 1024 << (extid & 0x3);
2343 extid >>= 2;
2344 /* Calc oobsize */
2345 mtd->oobsize = (8 << (extid & 0x03)) * (mtd->oobblock / 512);
2346 extid >>= 2;
2347 /* Calc blocksize. Blocksize is multiples of 64KiB */
2348 mtd->erasesize = (64 * 1024) << (extid & 0x03);
2349 extid >>= 2;
2350 /* Get buswidth information */
2351 busw = (extid & 0x01) ? NAND_BUSWIDTH_16 : 0;
2352
2353 } else {
...
2360 }
接下来判断设置的数据位宽是否与NAND芯片一致,不一致则将输出错误信息:
------- /drivers/nand/nand_base.c -------
2364 if (busw != (this->options & NAND_BUSWIDTH_16)) {
...
2374 }
然后对NAND Flash的其它信息进行设置(主要为OOB:Out Of Block和BBT:Bad Block Table等):
------- /drivers/nand/nand_base.c -------
2377 this->page_shift = ffs(mtd->oobblock) - 1;
...
2389 this->options |= NAND_NO_AUTOINCR;
然后判断NAND Flash是否为Samsung制造,否则将清除其大页操作方式;然后设置页擦除函数,这里执行的是else分支:
------- /drivers/nand/nand_base.c -------
2393 if (nand_maf_id != NAND_MFR_SAMSUNG && !nand_flash_ids[i].pagesize)
2394 this->options &= ~NAND_SAMSUNG_LP_OPTIONS;
2395
2396 /* Check for AND chips with 4 page planes */
2397 if (this->options & NAND_4PAGE_ARRAY)
2398 this->erase_cmd = multi_erase_cmd;
239 else
2400 this->erase_cmd = single_erase_cmd;
- NAND_MFR_SAMSUNG定义在/include/linux/mtd/nand.h中:
------- /include/linux/mtd/nand.h -------
346 #define NAND_MFR_SAMSUNG 0xec
- single_erase_cmd通过向K9F2G08U0C写入擦除命令(60h、d0h),对NAND Flash进行块擦除:
------- /drivers/nand/nand_base.c -------
2053 static void single_erase_cmd (struct mtd_info *mtd, int page)
2054 {
2055 struct nand_chip *this = mtd->priv;
2056 /* Send commands to erase a block */
2057 this->cmdfunc (mtd, NAND_CMD_ERASE1, -1, page);
2058 this->cmdfunc (mtd, NAND_CMD_ERASE2, -1, -1);
2059 }
然后判断NAND Flash的命令地址函数(this->cmdfunc)是否设置正确:
------- /drivers/nand/nand_base.c -------
2403 if (mtd->oobblock > 512 && this->cmdfunc == nand_command)
2404 this->cmdfunc = nand_command_lp;
最后获取制造商的名称("Samsung"):
------- /drivers/nand/nand_base.c -------
2407 for (j = 0; nand_manuf_ids[j].id != 0x0; j++) {
2408 if (nand_manuf_ids[j].id == nand_maf_id)
2409 break;
2410 }
2411 break;
2412 } //for (i = 0; nand_flash_ids[i].name != NULL; i++) {
至此,通过for循环查找NAND Flash结束,并且已经获取了NAND Flash的相关信息与设置。
下面对NAND Flash的名称再次校验,如果没有名称的话说明没有找到对应的NAND Flash,程序退出:
------- /drivers/nand/nand_base.c -------
2414 if (!nand_flash_ids[i].name) {
2415 printk (KERN_WARNING "No NAND device found!!!n");
2416 this->select_chip(mtd, -1);
2417 return 1;
2418 }
接下来读取所有的NAND Flash芯片的设备ID,这里只有一块NAND Flash,因此maxchips=1,不需进行读取:
------- /drivers/nand/nand_base.c -------
2420 for (i=1; i < maxchips; i++) {
...
2430 }
当有1块以上的NAND Flash芯片时,将输出其数量:
------- /drivers/nand/nand_base.c -------
2431 if (i > 1)
2432 printk(KERN_INFO "%d NAND chips detectedn", i);
第2435行到2496行进行OOB相关的设置,然后第2503行到2586行进行ECC(Error Checking and Correction)相关的设置。
第2597行取消NAND Flash的使能,在读取设备ID时(第2311行)使能了NAND Flash,因此需要取消:
------- /drivers/nand/nand_base.c -------
2597 this->select_chip(mtd, -1);
第2603行到2633行设置MTD相关的设置信息与操作函数。
nand_scan函数的最后调用nand_defalut_bbt函数(this->scan_bbt),nand_default_bbt函数位于/drivers/nand/nand_bbt.c文件中,在此函数中调用nand_scan_bbt函数;在nand_scan_bbt函数中,调用nand_memory_bbt函数重新扫描整个NAND Flash从而在内存中建立一个新的BBT;在nand_memory_bbt中调用create_bbt函数在内存中创建BBT;在create_bbt函数中调用nand_read_raw函数(位于/drivers/nand/nand.c文件中)来读取NAND Flash的块(Block):
- nand_scan 调用 nand_default_bbt(this->scan_bbt);
- nand_default_bbt 调用 nand_scan_bbt;
- nand_scan_bbt 调用 nand_memory_bbt;
- nand_memory_bbt 调用 create_bbt;
- create_bbt 调用 nand_read_raw。
至此,nand_scan函数分析修改完毕,通过读取NAND Flash设备ID,获取NAND Flash相关信息:包括制造商、名称、NAND Flash的结构(页大小等)等。
nand_scan函数结束后返回nand_init_chip,再返回到nand_init函数中,并打印出相关信息,在这里可以添加如下修改(非必要),打印NAND Flash的一些信息,例如:
------- /drivers/nand/nand.c -------
70 printf("%s ", nand_info[nand_curr_device].name);
至此,nand_init函数相关分析已经完成,编译生成u-boot.bin,烧写到MINI2440开发板上已经可以看到输出信息,并且进入了U-Boot控制台: