1.uboot本身是裸机程序
(1)在裸机中本来是没有驱动概念的(狭义的驱动概念是指在操作系统中用来具体操控硬件的那部分代码叫驱动)。
(2)裸机程序是直接操控硬件的,操作系统必须通过驱动来操控硬件。这两个有什么区别?本质区别就是分层。
2.uboot虚拟地址对硬件操作的影响
(1)操作系统(指的是linux)下MMU肯定是开启的,也就是说linux驱动中使用的都是虚拟地址,而纯裸机程序中根本不会开MMU,全部使用的是物理地址。这是裸机和操作系统中操控硬件的一个重要区别。
(2)uboot早期也是工作在纯物理地址下,但是现在的uboot开启了MMU,做了虚拟地址映射,因此做驱动时必须考虑到这一点。
通过查看uboot中的虚拟地址映射表,发现除了0x30000000~0x3FFFFFFF映射到0xC0000000~0xCFFFFFFF之外,其余的地址空间全部是原样映射,而在驱动中主要是操控硬件寄存器,而s5pv210的SFR都在0xEXXXXXXX地址空间,因此在驱动不需要考虑虚拟地址。
3.uboot借用(移植)linux驱动
(1)linux驱动本身做了模块化设计,linux驱动本身和linux内核不是强耦合的,这是linux驱动可以被uboot借用(移植)的关键。
(2)uboot移植了linux驱动源代码,uboot是从源代码级别去移植linux驱动的,这就是linux系统的开源性。
(3)uboot中的硬件驱动比linux简单,linux驱动本身有复杂的框架,需要实现更多的附带功能,而uboot本质上只是个裸机程序,uboot移植linux驱动时,只是借用了linux驱动的一部分而已。
1.从start_armboot开始
(1)驱动整体比较庞大,涉及很多文件夹下的很多文件,函数很多,贸然插入根本不知道看哪里,学习时必须有时序。因此这里的iNand/SD卡驱动从start_armboot开始。
(2)在start_armboot函数调用mmc_initialize函数实现mmc的初始化,该函数在lib_arm/board.c的603行被调用。
2.mmc_initialize
代码:1177 ~ 1208行(drivers/mmc/mmc.c)
int mmc_initialize(bd_t *bis)
{
struct mmc *mmc;
int err;
INIT_LIST_HEAD(&mmc_devices);
cur_dev_num = 0;
if (board_mmc_init(bis) < 0)
cpu_mmc_init(bis);
#if defined(DEBUG_S3C_HSMMC)
print_mmc_devices(',');
#endif
#ifdef CONFIG_CHECK_X210CV3
mmc = find_mmc_device(1);//lqm
#else
mmc = find_mmc_device(0);
#endif
if (mmc) {
err = mmc_init(mmc);
if (err)
err = mmc_init(mmc);
if (err) {
printf("Card init fail!\n");
return err;
}
}
printf("%ldMB\n", (mmc->capacity/(1024*1024/(1<<9))));
return 0;
}
(1)从名字可以看出,这个函数的作用就是初始化开发板上的MMC系统。MMC系统的初始化应该包含这几部分:SoC中的MMC控制器初始化(MMC系统时钟的初始化、SFR初始化)、SoC中的MMC相关的GPIO的初始化、iNand/SD卡芯片的初始化。
3.mmc_devices
(1)mmc_devices是一个链表(全局变量),用来记录系统中所有已注册的iNand/SD设备。所以向系统中插入一个iNand/SD设备,则系统驱动就会向mmc_devices链表中插入一个数据结构表示这个设备。
4.cpu_mmc_init
代码:232 ~ 241行(cpu/s5pc11x/cpu.c)
/*
* Initializes on-chip MMC controllers.
* to override, implement board_mmc_init()
*/
int cpu_mmc_init(bd_t *bis)
{
#ifdef CONFIG_S3C_HSMMC
setup_hsmmc_clock();
setup_hsmmc_cfg_gpio();
return smdk_s3c_hsmmc_init();
#else
return 0;
#endif
}
(1)该函数调用了3个函数:setup_hsmmc_clock、setup_hsmmc_cfg_gpio、smdk_s3c_hsmmc_init。
(2)setup_hsmmc_clock函数:位于cpu/s5pc11x/setup_hsmmc.c中,从名字上可以看出该函数是用来初始化SoC中MMC控制器的时钟部分的。
(3)setup_hsmmc_cfg_gpio函数:位于cpu/s5pc11x/setup_hsmmc.c中,从名字上可以该函数是用来配置SoC中MMC相关的GPIO的。
(4)smdk_s3c_hsmmc_init函数:位于drivers/mmc/s3c_hsmmc.c中,在它的内部通过宏定义USE_MMCx来决定是否调用s3c_hsmmc_initialize函数来进行具体的初始化操作。
(5)s3c_hsmmc_initialize函数:位于drivers/mmc/s3c_hsmmc.c中,代码如下所示:
static int s3c_hsmmc_initialize(int channel)
{
struct mmc *mmc;
mmc = &mmc_channel[channel];
sprintf(mmc->name, "S3C_HSMMC%d", channel);
mmc->priv = &mmc_host[channel];
mmc->send_cmd = s3c_hsmmc_send_command;
mmc->set_ios = s3c_hsmmc_set_ios;
mmc->init = s3c_hsmmc_init;
mmc->voltages = MMC_VDD_32_33 | MMC_VDD_33_34;
mmc->host_caps = MMC_MODE_4BIT | MMC_MODE_HS_52MHz | MMC_MODE_HS;
#if defined(USE_MMC0_8BIT) || defined(USE_MMC2_8BIT)
mmc->host_caps |= MMC_MODE_8BIT;
#endif
mmc->f_min = 400000;
mmc->f_max = 52000000;
mmc_host[channel].clock = 0;
switch(channel) {
case 0:
mmc_host[channel].ioaddr = (void *)ELFIN_HSMMC_0_BASE;
break;
case 1:
mmc_host[channel].ioaddr = (void *)ELFIN_HSMMC_1_BASE;
break;
case 2:
mmc_host[channel].ioaddr = (void *)ELFIN_HSMMC_2_BASE;
break;
#ifdef USE_MMC3
case 3:
mmc_host[channel].ioaddr = (void *)ELFIN_HSMMC_3_BASE;
break;
#endif
default:
printk("mmc err: not supported channel %d\n", channel);
}
return mmc_register(mmc);
}
函数解析:
第1点,定义并且实例化一个struct mmc类型的对象(定义了一个指针,并且给指针指向有意义的内存,或者给指针分配内存),然后填充它的各种成员,最后调用mmc_register函数来向驱动框架注册这个mmc设备驱动。
第2点,mmc_register功能是进行mmc设备的注册,注册方法其实就是将当前这个struct mmc使用链表连接到mmc_devices这个全局变量中去。
(6)在x210_sd.h中定义了USE_MMC0和USE_MMC2,因此在uboot初始化时,会调用2次s3c_hsmmc_initialize函数,传递参数分别是0和2,因此在调用完成之后,系统中会注册上2个mmc设备,表示当前系统中有2个mmc通道在工作。
5.find_mmc_device
代码:63 ~ 78行(drivers/mmc/mmc.c)
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;
}
printf("MMC Device %d not found\n", dev_num);
return NULL;
}
(1)这个函数其实就是通过mmc设备编号在系统中查找对应的mmc设备(struct mmc的对象,根据上面的分析可知,系统中有2个,分别是0和2)
(2)函数的工作原理就是通过遍历mmc_devices链表,依次寻找系统中注册的mmc设备,然后对比设备编号和我们当前要查找的设备编号,如果相同,就表示找到要找的设备。找到了之后,调用mmc_init函数来初始化它。
6.mmc_init
代码:1112 ~ 1144行(drivers/mmc/mmc.c)
int mmc_init(struct mmc *host)
{
int err;
err = host->init(host);
if (err)
return err;
/* Reset the Card */
err = mmc_go_idle(host);
if (err)
return err;
/* Test for SD version 2 */
err = mmc_send_if_cond(host);
/* Now try to get the SD card's operating condition */
err = mmc_send_app_op_cond(host);
/* If the command timed out, we check for an MMC card */
if (err == TIMEOUT) {
err = mmc_send_op_cond(host);
if (err)
return UNUSABLE_ERR;
} else
if (err)
return UNUSABLE_ERR;
return mmc_startup(host);
}
(1)该函数的功能是初始化mmc卡。
(2)函数的调用关系为:
mmc_init
mmc_go_idle
mmc_send_cmd
mmc_send_if_cond
mmc_send_cmd
······
从以上的分析可以看出,mmc_init函数内部就是依次通过向mmc卡发送命令码(CMD0、CMD2...)来初始化iNand/SD卡内部的控制器,以达到初始化SD卡的目的。
1.整个MMC的初始化分为2部分:SoC这一端的MMC控制器的初始化、SD卡这一端的本身的初始化。前一部分主要是在cpu_mmc_init函数中完成,后一部分主要是在mmc_init函数中完成。
2.整个初始化完成后去使用iNand/SD卡时,操作方法和mmc_init函数中初始化SD卡的的操作一样。读写SD卡时也是通过总线向SD卡发送命令、读取/写入数据来完成的。
3.顺着操作追下去,到了mmc_send_cmd函数处就断了,真正的向SD卡发送命令的硬件操作的函数找不到。这就是学习驱动的麻烦之处。
4.struct mmc结构体是关键,两部分初始化之间是用mmc结构体来链接的,初始化完了之后对mmc卡的常规读写操作也是通过mmc结构体来链接的。
1.数据结构
(1)驱动设计中有一个关键数据结构,例如MMC驱动的结构体struct mmc,这些结构体中包含一些变量和一些函数指针,变量用来记录驱动相关的一些属性,函数指针用来记录驱动相关的一些操作方法,这些变量和函数指针加起来就构成了驱动,驱动就被抽象为这个结构体。
(2)驱动工作的时候主要分为两部分:驱动构建(构建一个struct mmc,然后填充它)、驱动运行(调用结构体中变量和函数指针)
2.分离思想
(1)分离思想就是说在驱动中将操作方法和数据分开。
(2)操作方法就是函数,数据就是变量。所谓操作方法和数据分离的意思是:在不同的地方来存储和管理驱动的操作方法和变量,这样的优势就是驱动便于移植。
3.分层思想
(1)分层思想是指一个整体的驱动分为好多个层次,简单理解就是驱动分为很多个源文件,放在很多个文件夹中。例如本课程讲的mmc的驱动设计到drivers/mmc下面的2个文件和cpu/s5pc11x下面的几个文件。
(2)以mmc驱动为例来分析各个文件的作用:
drivers/mmc/mmc.c:本文件的主要内容是和MMC卡操作有关的方法,例如MMC卡设置为空闲状态、卡读写数据等,但是本文件中并没有具体的硬件操作函数,操作最终指向的是struct mmc结构体中的函数指针,这些函数指针是在驱动构建的时候和真正的硬件操作函数挂接的(真正的硬件操作函数在别的文件中)
drivers/mmc/s3c_hsmmc.c:本文件是SoC内部MMC控制器的硬件操作方法,例如向SD卡发送命令的函数(s3c_hsmmc_send_command)、SD卡读写数据的函数(s3c_hsmmc_set_ios)等,这些函数就是具体操作硬件的函数,也就是mmc.c中需要的那些硬件操作函数。这些函数在mmc驱动初始化构建时(s3c_hsmmc_initialize函数中)和struct mmc挂接起来备用。
分析:mmc.c和s3c_hsmmc.c构成了一个分层,mmc.c中调用了s3c_hsmmc.c中的函数,所以mmc.c在上层,s3c_hsmmc.c在下层。有这个分成后,可以发现mmc.c中不涉及具体硬件的操作,s3c_hsmmc.c中不涉及时序操作。因此移植的时候就有好处:例如要把这一套mmc驱动移植到别的SoC上的,那mmc.c就不用动,s3c_hsmmc.c动就可以了;又例如SoC没变,但是SD卡升级了,这时候只需要更换mmc.c,不需要更换s3c_hsmmc.c。
(3)cpu/s5pc11x下还有一个setup_hsmmc.c,也和MMC驱动有关,但是这些代码为什么不放到drivers目录下,而是放到cpu目录下?因为这里面的两个函数(setup_hsmmc_clock和setup_hsmmc_cfg_gpio)都是和SoC有关的初始化函数,这两个函数不能放到drivers目录下,实际上如果非把这两个函数放在drivers/mmc/s3c_hsmmc.c中也能凑合说得过去。