嵌入式Linux学习:u-boot源码分析(4)--AM335X系列的2014.10版

2019-07-13 08:11发布

    继续之前的第三篇,之前已经完成了I2C的引脚配置以及分频,接下来就要实际开始去检测和读取EEPROM里的数据。看下面的代码 /* * Read header information from EEPROM into global structure. */ static int read_eeprom(struct am335x_baseboard_id *header) { /* Check if baseboard eeprom is available *///wlg: all board based on ti's am335x has a eeprom to save the board's information! if (i2c_probe(CONFIG_SYS_I2C_EEPROM_ADDR)) { puts("Could not probe the EEPROM; something fundamentally " "wrong on the I2C bus. "); return -ENODEV;//wlg: at first we probe it is exist or not; } /* read the eeprom using i2c */ if (i2c_read(CONFIG_SYS_I2C_EEPROM_ADDR, 0, 2, (uchar *)header, sizeof(struct am335x_baseboard_id))) { puts("Could not read the EEPROM; something fundamentally" " wrong on the I2C bus. "); return -EIO; }//wlg: then we read the data from eeprom into point ----header, if (header->magic != 0xEE3355AA) { /* * read the eeprom using i2c again, * but use only a 1 byte address */ if (i2c_read(CONFIG_SYS_I2C_EEPROM_ADDR, 0, 1, (uchar *)header, sizeof(struct am335x_baseboard_id))) { puts("Could not read the EEPROM; something " "fundamentally wrong on the I2C bus. "); return -EIO; } if (header->magic != 0xEE3355AA) { printf("Incorrect magic number (0x%x) in EEPROM ", header->magic); return -EINVAL; } } return 0; }     注释已经说得比较清楚了,本函数分以下阶段:     1. 试探EEPROM是否存在     2. 如果存在,就读取相应长度的数据     3. 对读回的数据进行检测,如果读回的数据与预设一致,则返回0     4. 如果不一致,则再次进行读取,若还是失败就返回相应的标志     首先是第一步,通过判断i2c_probe(CONFIG_SYS_I2C_EEPROM_ADDR)的返回来判断EEPROM是否有效,实际上就是通过去检测I2C的slave设备是否会回复ACK信号来判断设备是否存在,这方面的知识请去了解下I2C的控制方式即可明白;具体的做法是执行i2c_read()函数做一次无效数据读取。在读取过程中会检测是否接收到ACK信号;     加入EEPROM设备是存在的,那么接下来就可以执行i2c_read(CONFIG_SYS_I2C_EEPROM_ADDR, 0, 2, (uchar *)header,  sizeof(struct am335x_baseboard_id))函数,很简单的,其函数原型如下,看注释应该非常清楚了,不需要解释了。 /*
 * Read/Write interface:
 *   chip:    I2C chip address, range 0..127
 *   addr:    Memory (register) address within the chip
 *   alen:    Number of bytes to use for addr (typically 1, 2 for larger
 *              memories, 0 for register type devices with only one
 *              register)
 *   buffer:  Where to read/write the data
 *   len:     How many bytes to read/write
 *
 *   Returns: 0 on success, not 0 on failure
 */
int i2c_read(uint8_t chip, unsigned int addr, int alen, uint8_t *buffer, int len)
    其结果就是读取外置的EEPROM设备上的偏移地址为0,长度为sizeof(struct am335x_baseboard_id) 的数据,保存到起始地址为(uchar *)header的内存空间。执行完毕后,header这个结构体里应该塞满了关于board信息的数据。然后就检查里面某个元素(header->magic)是否等于预设0xEE3355AA。正常情况都是会满足的!所以read_eeprom()函数返回0!     再次回到上一篇的get_dpll_ddr_params()函数。重复如下: if (board_is_evm_sk(&header)) return &dpll_ddr_evm_sk; else if (board_is_bone_lt(&header)) return &dpll_ddr_bone_black; else if (board_is_evm_15_or_later(&header)) return &dpll_ddr_evm_sk; else return &dpll_ddr;     上面的代码就很容易理解了,就是会去检测header这个结构体中的name元素是否与其一致,比如说 static inline int board_is_evm_sk(struct am335x_baseboard_id *header) { return !strncmp("A335X_SK", header->name, HDR_NAME_LEN); }    上面的代码就是对比header->name是否等于"A335X_SK",若相等就会返回1.这就意味着EEPROM中记录的数据,就有板子的名字等信息!     假如我们用的是Beaglebone Black板子的话,只有执行board_is_bone_lt(&header)才会返回1,其他检测函数只能返回0!     所以get_dpll_ddr_params()最终返回了dpll_ddr_bone_black这个指针!     其他的代码就不一一解释了,其大概的作用就是,根据header(从EEPROM中读回)结构体的数据,有选择的进行引脚配置,包括SDRAM,MII设备的初始化。     请注意,不同的board读回的header是不一样的,所以其相关的底层硬件配置也略有不同,所以如果要自定义一块板子,那么将这个EEPROM里的内容覆盖成满足你板子要求的相应参数即可。或者可以跳过EEPROM的读写,直接用我们的配置参数代替,因为用户的板子一般都不会有那么灵活的要求。      最终我们跳出了board_early_init_f()函数,开始执行下一个函数sdram_init()(定义在board iam335xoard.c) void sdram_init(void) { __maybe_unused struct am335x_baseboard_id header; if (read_eeprom(&header) < 0) puts("Could not get board ID. "); //wlg: get the header from eeprom, it has information which board we now use if (board_is_evm_sk(&header)) { /* * EVM SK 1.2A and later use gpio0_7 to enable DDR3. * This is safe enough to do on older revs. */ gpio_request(GPIO_DDR_VTT_EN, "ddr_vtt_en"); gpio_direction_output(GPIO_DDR_VTT_EN, 1); } if (board_is_evm_sk(&header)) config_ddr(303, &ioregs_evmsk, &ddr3_data, &ddr3_cmd_ctrl_data, &ddr3_emif_reg_data, 0); else if (board_is_bone_lt(&header))//wlg: this branch will return true, config_ddr(400, &ioregs_bonelt,//wlg: then will initial sdram as BBB! &ddr3_beagleblack_data, &ddr3_beagleblack_cmd_ctrl_data, &ddr3_beagleblack_emif_reg_data, 0); else if (board_is_evm_15_or_later(&header)) config_ddr(303, &ioregs_evm15, &ddr3_evm_data, &ddr3_evm_cmd_ctrl_data, &ddr3_evm_emif_reg_data, 0); else config_ddr(266, &ioregs, &ddr2_data, &ddr2_cmd_ctrl_data, &ddr2_emif_reg_data, 0); } #endif    很明显,上面的代码和之前的代码非常相似,实际上就是读取EEPROM里的内容放置到header中,这里又读了一次,估计是想要实现每一步都是独立的。其顺序也大致相同,EEPROM里的数据读回来以后,就通过半段header->name来判断板子的类型,根据类型有选择进行初始化,也就是DDR的初始化,具体的实现方式应该就是底层寄存器的操作,这里不展开。 -------S_init()执行完毕,返回lowlevel_init,在返回_reset,也就是返回start.S--------- 总结下都实现了什么:     1. 实现了一个可用与console的uart,可以输出简单的信息,帮助调试!     2. 读取EEPROM,按照类型配置SDRAM,为下一步将u-boot镜像复制到SDRAM空间里做准备 --------------------------------返回start.S-------------------------------------------     又回到了汇编部分, bl _main /*wlg: jump to arch/arm/lib/crt0.s*/    就是一个转跳,再次展开_main函数 /* * entry point of crt0 sequence */ ENTRY(_main) /* * Set up initial C runtime environment and call board_init_f(0). */ #if defined(CONFIG_SPL_BUILD) && defined(CONFIG_SPL_STACK) ldr sp, =(CONFIG_SPL_STACK)/* wlg: in spl, it seems be 0x40310000-sizeof(global_data)*/ #else ldr sp, =(CONFIG_SYS_INIT_SP_ADDR)/* wlg: in uboot, it will be ?*/ #endif bic sp, sp, #7 /* 8-byte alignment for ABI compliance | SPL | uboot |*/ mov r2, sp /* wlg: we record the end of address of the initial |sp is useful, r9 (gdata) will |sp is useful, a new temp |*/ sub sp, sp, #GD_SIZE /* allocate one GD above SP |be redefined in board_init_f |GD(pointed by r9) will be |*/ bic sp, sp, #7 /* 8-byte alignment for ABI compliance |so next text is only to clear | set above on sp, gdata |*/ mov r9, sp /* GD is above SP |a invalid memory |be discarded |*/ mov r1, sp /* wlg: we record the start address of the initial*/ mov r0, #0 /*wlg : the num of initialition*/ clr_gd: cmp r1, r2 /* while not at end of GD */ strlo r0, [r1] /* clear 32-bit GD word */ /*wlg: ro >> [r1]*/ addlo r1, r1, #4 /* move to next */ blo clr_gd #if defined(CONFIG_SYS_MALLOC_F_LEN) && !defined(CONFIG_SPL_BUILD) sub sp, sp, #CONFIG_SYS_MALLOC_F_LEN str sp, [r9, #GD_MALLOC_BASE] #endif /* mov r0, #0 not needed due to above code */ bl board_init_f /*wlg: SPL: board_init_f - Function in spl.c (archarmlib) , and it will not return, it will jump to uboot's start's*/ /*wlg: Uboot: board_init_f - Function in Board.c (archarmlib) at line 263 (199 lines), it will return*/     上面的代码我添加了比较详细的备注,在这里SPL和uboot的执行目的有很大的不同,我们这里只谈SPL这个阶段,所以上述代码主要是为了完成以下工作:     1. 重新确定了sp,这是为了下一步的C语言函数调用做准备,貌似之前也有做过,所以不解释了     2. 在sp上划出了一部分空间作为保存全局数据的空间(划分完以后sp=sp-GD_SIZE),并将其请0!即使这里将sp(减过后)赋给了r9,但是这部分空间目前在SPL阶段没有被利用起来,在后面又会重新定义r9,再次将其指向我们之前一直提及的gdata,而不会沿用这里的sp!     3. 跳到 board_init_f ()函数执行接下来的代码,在SPL阶段,这个函数被定义在spl.c (archarmlib),在uboot阶段被定义在另一个地方,看注释,以后再解释。接下来我们跳到board_init_f()函数继续: /* * In the context of SPL, board_init_f must ensure that any clocks/etc for * DDR are enabled, ensure that the stack pointer is valid, clear the BSS * and call board_init_f. We provide this version by default but mark it * as __weak to allow for platforms to do this in their own way if needed. */ void __weak board_init_f(ulong dummy) { /* Clear the BSS. */ memset(__bss_start, 0, __bss_end - __bss_start); /* Set global data pointer. */ gd = &gdata; board_init_r(NULL, 0);//wlg: SPL: board_init_r - Function in spl.c (archarmlib) }
    看注释很好理解,完成以下工作:
    1. 清除BSS段
    2. 又将gd指针指向了gdata,也就是将r9指向了gdata     3.跳到board_init_r()函数继续执行,这个函数被定义在spl.c (archarmlib)  void board_init_r(gd_t *dummy1, ulong dummy2) { u32 boot_device; debug(">>spl:board_init_r() "); #ifdef CONFIG_SYS_SPL_MALLOC_START mem_malloc_init(CONFIG_SYS_SPL_MALLOC_START, CONFIG_SYS_SPL_MALLOC_SIZE); #endif #ifndef CONFIG_PPC /* * timer_init() does not exist on PPC systems. The timer is initialized * and enabled (decrementer) in interrupt_init() here. */ timer_init(); #endif #ifdef CONFIG_SPL_BOARD_INIT spl_board_init();//wlg: skip it now, we will look into it later #endif //return gd->arch.omap_boot_params.omap_bootdevice boot_device = spl_boot_device();//this parament saved early: save_omap_boot_params() debug("boot device - %d ", boot_device); switch (boot_device) { #ifdef CONFIG_SPL_RAM_DEVICE case BOOT_DEVICE_RAM: spl_ram_load_image(); break; #endif #ifdef CONFIG_SPL_MMC_SUPPORT case BOOT_DEVICE_MMC1: case BOOT_DEVICE_MMC2: case BOOT_DEVICE_MMC2_2: spl_mmc_load_image(); //wlg: for BBB, we will boot from MMC, whether MMC or SD break; //after this, we copy the u-boot to sdram with offset at ..... #endif #ifdef CONFIG_SPL_NAND_SUPPORT case BOOT_DEVICE_NAND: spl_nand_load_image(); break; #endif #ifdef CONFIG_SPL_ONENAND_SUPPORT case BOOT_DEVICE_ONENAND: spl_onenand_load_image(); break; #endif #ifdef CONFIG_SPL_NOR_SUPPORT case BOOT_DEVICE_NOR: spl_nor_load_image(); break; #endif #ifdef CONFIG_SPL_YMODEM_SUPPORT case BOOT_DEVICE_UART: spl_ymodem_load_image(); break; #endif #ifdef CONFIG_SPL_SPI_SUPPORT case BOOT_DEVICE_SPI: spl_spi_load_image(); break; #endif #ifdef CONFIG_SPL_ETH_SUPPORT case BOOT_DEVICE_CPGMAC: #ifdef CONFIG_SPL_ETH_DEVICE spl_net_load_image(CONFIG_SPL_ETH_DEVICE); #else spl_net_load_image(NULL); #endif break; #endif #ifdef CONFIG_SPL_USBETH_SUPPORT case BOOT_DEVICE_USBETH: spl_net_load_image("usb_ether"); break; #endif #ifdef CONFIG_SPL_USB_SUPPORT case BOOT_DEVICE_USB: spl_usb_load_image(); break; #endif #ifdef CONFIG_SPL_SATA_SUPPORT case BOOT_DEVICE_SATA: spl_sata_load_image(); break; #endif default: debug("SPL: Un-supported Boot Device "); hang(); } switch (spl_image.os) { case IH_OS_U_BOOT: debug("Jumping to U-Boot "); break; #ifdef CONFIG_SPL_OS_BOOT case IH_OS_LINUX: debug("Jumping to Linux "); spl_board_prepare_for_linux(); jump_to_image_linux((void *)CONFIG_SYS_SPL_ARGS_ADDR); #endif default: debug("Unsupported OS image.. Jumping nevertheless.. "); } jump_to_image_no_args(&spl_image);//now we jump from spl to uboot }     这段代码主要是完成以下工作:     1. 根据之前保存的boot parament参数来从哪一个设备读取uboot的镜像,这里有很多种加载镜像的途径,可以从MMC,也可以从网络,串口等     2. 将uboot的镜像复制到SDRAM的相应位置后,然后跳转到SDRAM中执行。依旧是说,我们之前SPL程序都是在SRAM中执行的,从现在开始,终于跳转到SDRAM中运行了!     接下来详细的介绍下上述步骤:     本函数最早被执行的,实际上是这个语句:         boot_device = spl_boot_device();//this parament saved early: save_omap_boot_params()     检查下spl_boot_device()函数,     u32 spl_boot_device(void) { return (u32) (gd->arch.omap_boot_params.omap_bootdevice); }    也就是最后就是boot_device  = gd->arch.omap_boot_params.omap_bootdevice,这个gd里的参数是之前保存的,它记录的就是加载SPL 的设备,有可能是UART,MMC,网络等等,都会如实的被保存在这个参数里。因为我们的板子是BBBlack,假设是从MMC的SD中完成SPL的加载,那么接下来被执行的就是     spl_mmc_load_image(); //wlg: for BBB, we will boot from MMC, whether MMC  or SD  void spl_mmc_load_image(void) { struct mmc *mmc; int err; u32 boot_mode; mmc_initialize(gd->bd); /* We register only one device. So, the dev id is always 0 */ mmc = find_mmc_device(0);//it is SPL stage, we only have one mmc. if (!mmc) { #ifdef CONFIG_SPL_LIBCOMMON_SUPPORT puts("spl: mmc device not found!! "); #endif hang(); } err = mmc_init(mmc);//if mmc initial is complete, it will return 0 if (err) { #ifdef CONFIG_SPL_LIBCOMMON_SUPPORT printf("spl: mmc init failed: err - %d ", err); #endif hang(); } boot_mode = spl_boot_mode();//recognize boot mode from global_data if (boot_mode == MMCSD_MODE_RAW) { debug("boot mode - RAW "); #ifdef CONFIG_SPL_OS_BOOT if (spl_start_uboot() || mmc_load_image_raw_os(mmc)) #endif err = mmc_load_image_raw(mmc, CONFIG_SYS_MMCSD_RAW_MODE_U_BOOT_SECTOR); #ifdef CONFIG_SPL_FAT_SUPPORT } else if (boot_mode == MMCSD_MODE_FAT) { debug("boot mode - FAT "); #ifdef CONFIG_SPL_OS_BOOT if (spl_start_uboot() || spl_load_image_fat_os(&mmc->block_dev, CONFIG_SYS_MMC_SD_FAT_BOOT_PARTITION)) #endif err = spl_load_image_fat(&mmc->block_dev, CONFIG_SYS_MMC_SD_FAT_BOOT_PARTITION, CONFIG_SPL_FAT_LOAD_PAYLOAD_NAME); #endif #ifdef CONFIG_SUPPORT_EMMC_BOOT } else if (boot_mode == MMCSD_MODE_EMMCBOOT) { /* * We need to check what the partition is configured to. * 1 and 2 match up to boot0 / boot1 and 7 is user data * which is the first physical partition (0). */ int part = (mmc->part_config >> 3) & PART_ACCESS_MASK; if (part == 7) part = 0; if (mmc_switch_part(0, part)) { #ifdef CONFIG_SPL_LIBCOMMON_SUPPORT puts("MMC partition switch failed "); #endif hang(); } #ifdef CONFIG_SPL_OS_BOOT if (spl_start_uboot() || mmc_load_image_raw_os(mmc)) #endif err = mmc_load_image_raw(mmc, CONFIG_SYS_MMCSD_RAW_MODE_U_BOOT_SECTOR); #endif } else { #ifdef CONFIG_SPL_LIBCOMMON_SUPPORT puts("spl: wrong MMC boot mode "); #endif hang(); } if (err) hang(); }     也就是说所我们选择从MMC中加载uboot,要做的工作就是:     1. mmc_initialize(gd->bd);     2. mmc = find_mmc_device(0);//it is SPL stage, we only have one mmc.     3. err = mmc_init(mmc);//if mmc initial is complete, it will return 0     4.  boot_mode = spl_boot_mode();//recognize boot mode from global_data     5. 根据boot_mode,将uboot的镜像复制到SDRAM中,返回     详细的功能介绍,会在下一篇中继续介绍。