源码 移植_U-Boot-1.1.6移植到MINI2440开发板(4) —— 源码分析第二阶段(1)

下面开始分析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来改变时钟频率:

7a562e1437956395c348df1913539425.png

修改时钟设置宏定义如下:

------- /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:

513000d3a28c459f4f70a270e6201b27.png

75a6bb8be96a6536238fef45cf43a296.png
  • 在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时钟信号,计算公式如下:

76339fbc69bd06d97396b87bbefae5b6.png
  • 第63行对UART控制寄存器进行设置:

7044297366b368a43c541c2097c0a0a1.png
  • 第64行对UART MODEM寄存器进行设置:

c913522fd4b636fe3653467ea9b8ac8e.png
  • 第66行对UART line控制寄存器进行设置,无校验,1位停止位,8位数据位:

39112cac2f39a00d985bc8e16c467973.png
  • 第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操作如下:

a05d66778a83ec3b2ac3afc156f8ca55.png

d22dd2423c8c800a2a3bad22eb8001e3.png
  • 读取时首先读到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 }

cbed8b99e839fc305603b77205e2ed0e.png

然后判断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控制台:

d2ace5b6d5224b5141ee0f544e328e0f.png
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值