继续之前的第三篇,之前已经完成了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中,返回
详细的功能介绍,会在下一篇中继续介绍。