之前的博客已经写到SPL阶段中关于MMC读取uboot的image的部分,我们将其简单的复制如下:
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中,返回
首先是第一个功能, mmc_initialize(gd->bd);这个函数在drivers\mmc\mmc.c中定义,如下
int mmc_initialize(bd_t *bis)
{
INIT_LIST_HEAD (&mmc_devices);//mmc_devices is a struct: list_head, which keep a next and a prev
cur_dev_num = 0;
if (board_mmc_init(bis) < 0)// arch/arm/cpu/armv7/Omap-common/boot-common.c,
cpu_mmc_init(bis); //after this board_mmc_init, we updata the mmc_devices link!
#ifndef CONFIG_SPL_BUILD
print_mmc_devices(',');//int the stage of SPL, print is not function.
#endif
do_preinit();//call mmc.init to make initialize mmc,
return 0;
}
需要详细的介绍下mmc_devices这个变量:
static struct list_head mmc_devices;
实际上mmc_devices只是一个链表结构体而已,这个链表结构体名为list_head ,继续看list_head :
struct list_head {
struct list_head *next, *prev;
};
而链表结构体里面保存着两个元素:
1. 指向前一个链表的指针
2. 指向后一个链表的指针
回过头继续看mmc_initialize()函数,其完成的工作大致如下:
初始化一个名为mmc_devices的全局(链表)变量,即INIT_LIST_HEAD (&mmc_devices),这个函数实际上只是将mmc_devices这个结构体里的两个指针赋值为mmc_devices,即默认前一个链表和后一个链表都是自己,即为初始化状态!
将全局变量cur_dev_num 赋值为0,这个变量记录着当前mmc设备的数量,目前mmc设备还没有被发现,所以默认数量就是0
接着执行board_mmc_init(bis)函数,这个函数定义在arch/arm/cpu/armv7/Omap-common/boot-common.c,如下:
int board_mmc_init(bd_t *bis)//this function is initial the mmc which chose by SYS_BOOT...
{
switch (spl_boot_device()) {
case BOOT_DEVICE_MMC1:
omap_mmc_init(0, 0, 0, -1, -1);
break;
case BOOT_DEVICE_MMC2:
case BOOT_DEVICE_MMC2_2:
omap_mmc_init(1, 0, 0, -1, -1);
break;
}
return 0;
}
这个函数就是一个switch分支结构,选择的依据就是spl_boot_device()的返回,其定义也在boot-common.c内,如下:
u32 spl_boot_device(void)
{
return (u32) (gd->arch.omap_boot_params.omap_bootdevice);
}
也就是返回了全局变量中的某一个元素!这个元素在之前的博客中有介绍,它表示MLO(就是SPL)被保存的地方。由于之前是采用默认的配置方式,这里要做进一步的mmc初始化!
int omap_mmc_init(int dev_index, uint host_caps_mask, uint f_max, int cd_gpio,
int wp_gpio)
{
struct mmc *mmc;
struct omap_hsmmc_data *priv_data;
struct mmc_config *cfg;
uint host_caps_val;
priv_data = malloc(sizeof(*priv_data));
if (priv_data == NULL)
return -1;
host_caps_val = MMC_MODE_4BIT | MMC_MODE_HS_52MHz | MMC_MODE_HS |
MMC_MODE_HC;
switch (dev_index) {
case 0:
priv_data->base_addr = (struct hsmmc *)OMAP_HSMMC1_BASE;
break;
#ifdef OMAP_HSMMC2_BASE
case 1:
priv_data->base_addr = (struct hsmmc *)OMAP_HSMMC2_BASE;
#if (defined(CONFIG_OMAP44XX) || defined(CONFIG_OMAP54XX) || \
defined(CONFIG_DRA7XX)) && defined(CONFIG_HSMMC2_8BIT)
/* Enable 8-bit interface for eMMC on OMAP4/5 or DRA7XX */
host_caps_val |= MMC_MODE_8BIT;
#endif
break;
#endif
#ifdef OMAP_HSMMC3_BASE
case 2:
priv_data->base_addr = (struct hsmmc *)OMAP_HSMMC3_BASE;
#if defined(CONFIG_DRA7XX) && defined(CONFIG_HSMMC3_8BIT)
/* Enable 8-bit interface for eMMC on DRA7XX */
host_caps_val |= MMC_MODE_8BIT;
#endif
break;
#endif
default:
priv_data->base_addr = (struct hsmmc *)OMAP_HSMMC1_BASE;
return 1;
}
#ifdef OMAP_HSMMC_USE_GPIO
/* on error gpio values are set to -1, which is what we want */
priv_data->cd_gpio = omap_mmc_setup_gpio_in(cd_gpio, "mmc_cd");
priv_data->wp_gpio = omap_mmc_setup_gpio_in(wp_gpio, "mmc_wp");
#endif
cfg = &priv_data->cfg;
cfg->name = "OMAP SD/MMC";
cfg->ops = &omap_hsmmc_ops;
cfg->voltages = MMC_VDD_32_33 | MMC_VDD_33_34 | MMC_VDD_165_195;
cfg->host_caps = host_caps_val & ~host_caps_mask;
cfg->f_min = 400000;
if (f_max != 0)
cfg->f_max = f_max;
else {
if (cfg->host_caps & MMC_MODE_HS) {
if (cfg->host_caps & MMC_MODE_HS_52MHz)
cfg->f_max = 52000000;
else
cfg->f_max = 26000000;
} else
cfg->f_max = 20000000;
}
cfg->b_max = CONFIG_SYS_MMC_MAX_BLK_COUNT;
#if defined(CONFIG_OMAP34XX)
/*
* Silicon revs 2.1 and older do not support multiblock transfers.
*/
if ((get_cpu_family() == CPU_OMAP34XX) && (get_cpu_rev() <= CPU_3XX_ES21))
cfg->b_max = 1;
#endif
mmc = mmc_create(cfg, priv_data);//after this, we linked the new mmc to mmc_devices.
if (mmc == NULL)
return -1;
return 0;
}
这个函数比较长,其主要完成以下工作:
a. 定义函数内所需要的指针变量
b. 在SRAM内申请一片内存空间,用来保存omap_hsmmc_data 这个结构体,申请成功后将其首地址赋值给priv_data这个指针,来了解一下这个结构体:
struct omap_hsmmc_data {
struct hsmmc *base_addr;
struct mmc_config cfg;
#ifdef OMAP_HSMMC_USE_GPIO
int cd_gpio;
int wp_gpio;
#endif
};
其主要包含了两个重要的元素,第一个是指向片上控制mmc的寄存器的指针,第二个是用来保存mmc配置信息的mmc_config 结构体cfg,再来看一下该结构体:
struct mmc_config {
const char *name;
const struct mmc_ops *ops;
uint host_caps;
uint voltages;
uint f_min;
uint f_max;
uint b_max;
unsigned char part_type;
};
该结构体包含了关于mmc的配置信息,包括名字等等;那么回到omap_mmc_init()函数
c. 根据传入的变量值,来选择片上的哪一个mmc控制寄存器作为其所实际控制寄存器。因为片上可能有2个或以上的mmc控制器,每一个控制器都对应着某一个mmc卡或者SD卡,所以要根据实际的需要来选择哪一个控制器,比如说这里我们选择:
case 0:
priv_data->base_addr = (struct hsmmc *)OMAP_HSMMC1_BASE;
就是将上面的priv_data->base_addr赋值为OMAP_HSMMC1_BASE,这个值实际上就是对应着片上的外设寄存器的首地址!
e. 执行mmc = mmc_create(cfg, priv_data);//after this, we linked the new mmc to mmc_devices.
struct mmc *mmc_create(const struct mmc_config *cfg, void *priv)
{
struct mmc *mmc;
/* quick validation */
if (cfg == NULL || cfg->ops == NULL || cfg->ops->send_cmd == NULL ||
cfg->f_min == 0 || cfg->f_max == 0 || cfg->b_max == 0)
return NULL;
mmc = calloc(1, sizeof(*mmc));
if (mmc == NULL)
return NULL;
mmc->cfg = cfg;
mmc->priv = priv;
/* the following chunk was mmc_register() */
/* Setup dsr related values */
mmc->dsr_imp = 0;
mmc->dsr = 0xffffffff;
/* Setup the universal parts of the block interface just once */
mmc->block_dev.if_type = IF_TYPE_MMC;
mmc->block_dev.dev = cur_dev_num++;
mmc->block_dev.removable = 1;
mmc->block_dev.block_read = mmc_bread;
mmc->block_dev.block_write = mmc_bwrite;
mmc->block_dev.block_erase = mmc_berase;
/* setup initial part type */
mmc->block_dev.part_type = mmc->cfg->part_type;
INIT_LIST_HEAD(&mmc->link);
list_add_tail(&mmc->link, &mmc_devices);//to make a linked between new device: mmc
//and default: mmc_devices
return mmc;
}
这个函数的主要功能是:
a. 在SRAM上申请内存用来存放mmc这个结构体,mmc这个结构体中包含了上面所说的priv结构体,cfg结构体等等,也包含了一个链表
b.将之前定义的priv和cfg赋值给mmc里的同名元素
整个效果就如下图:
c.给mmc结构体中的其他元素赋值
d. 初始化其链表
e. 将mmc的链表和之前已经存在的链表进行连接
连接的过程如下:
上图是个广泛的例子,就是mmc_device作为链表的结尾。其next是指向自己,其prev是指向新加入的mmc结构体,在这里就是上面定义的mmc。如果此时要再加入一个mmc2,那么效果也如上面右边所示,也就是说mmc_device总是在链表的最后,而新加入的链表总是在mmc_devices的旁边!
这样我们就完成了mmc的初始化,这个初始化包括其配置信息以及链表信息等等
这个函数定义在mmc.c文件中
static void do_preinit(void)
{
struct mmc *m;
struct list_head *entry;
list_for_each(entry, &mmc_devices) {
m = list_entry(entry, struct mmc, link);
if (m->preinit)
mmc_start_init(m);
}
}
这个函数实际上就是历遍mmc_devices这个链表头,提取出有效的mmc地址并赋值给m这个指针,然后利用mmc_start_init(m)进行初始化。
---------------------完成mmc的初始化--------------------
struct mmc *find_mmc_device(int dev_num)
{
struct mmc *m;
struct list_head *entry;
list_for_each(entry, &mmc_devices) {
m = list_entry(entry, struct mmc, link);
if (m->block_dev.dev == dev_num)
return m;
}
#if !defined(CONFIG_SPL_BUILD) || defined(CONFIG_SPL_LIBCOMMON_SUPPORT)
printf("MMC Device %d not found\n", dev_num);
#endif
return NULL;
}
实际上也是历遍mmc_devices链表,将其中符合要求的(就是索引为0,代表默认mmc设备,在这里只有一个,所以其默认就是0)mmc设备结构体指针返回
int mmc_init(struct mmc *mmc)
{
int err = IN_PROGRESS;
unsigned start;
if (mmc->has_init)
return 0;
start = get_timer(0);
if (!mmc->init_in_progress)
err = mmc_start_init(mmc);
if (!err || err == IN_PROGRESS)
err = mmc_complete_init(mmc);
debug("%s: %d, time %lu\n", __func__, err, get_timer(start));
return err;
}
实际上就是通过全局函数去判断mmc是否初始化完成,并记录初始化完成的时间。这个函数和之前的mmc_start_init(m);项对应!
err = spl_load_image_fat(&mmc->block_dev,
CONFIG_SYS_MMC_SD_FAT_BOOT_PARTITION,
CONFIG_SPL_FAT_LOAD_PAYLOAD_NAME);
根据选择文件类型,或raw或者是fat类型,将mmc设备中的uboot镜像加载到SDRAM中,至此完成了uboot镜像从mmc中加载到SDRAM中。但是目前程序任然还是SPL阶段,所以CPU的指针还是指向SRAM
-----------------uboot镜像加载完成------------------------
回到开始的board_init_r(),接下来继续执行jump_to_image_no_args(&spl_image);//now we jump from spl to uboot
spl_image.entry_point = image_get_ep(header);
然后程序就从SPL阶段跳转到Uboot阶段,且CPU从SRAM跳到了SDRAM中执行!
后期的博客继续续写uboot阶段!