一、uboot与linux驱动
1.1、uboot本身是裸机程序
(1)狭义的驱动概念是指:操作系统中用来具体操控硬件的代码叫驱动
广义的驱动概念是指:凡是操控硬件的代码都叫驱动
(2)裸机程序中是直接使用寄存器的物理地址来操控硬件的,操作系统中必须通过驱动来操控硬件。
这两个有什么区别?本质区别就是分层。
1.2、uboot的虚拟地址对硬件操作的影响
(1)操作系统(指的是linux)下MMU肯定是开启的,也就是说linux驱动中使用的都是虚拟地址。
而纯裸机程序中根本没有使用MMU,全部使用的是物理地址。这是裸机和驱动操控硬件的一个重要区别。
(2)uboot早期也是使用纯物理地址工作的,但是现在的uboot开启了MMU做了虚拟地址映射,因此要将驱动移植到uboot必须考虑真实操控的是不是同一个物理地址。
查uboot中的虚拟地址映射表,发现除了0x30000000-0x3FFFFFFF映射到了0xC0000000-0xCFFFFFFF之外,其余的虚拟地址空间全是原样映射的。
而我们驱动中主要是操控硬件寄存器,而S5PV210的SFR都在0xExxxxxx地址空间,因此驱动中不必考虑虚拟地址。
1.3、uboot借用(移植)了linux驱动
(1)linux驱动本身做了分文件的模块化设计。linux驱动本身和linux内核不是强耦合的,这是linux驱动可以被uboot借用(移植)的关键。
(2)linux驱动本身有更复杂的框架,实现了很多的附带功能,而uboot本质上只是个裸机程序,uboot移植linux驱动时只是借用了linux驱动的一部分而已。
二、iNand/SD驱动解析(从start_armboot中的mmc_initialize开始)
(1)驱动整体非常庞大,涉及很多个文件夹下的很多文件,函数更多,我们从start_armboot函数中的驱动初始化部分入手。
(2)mmc_initialize在start_armboot函数604行处调用,函数定义在:uboot/drivers/mmc/mmc.c中。
uboot移植的linux驱动都放在了uboot/drivers目录。
int mmc_initialize(bd_t *bis)
{
struct mmc *mmc;
int err;
INIT_LIST_HEAD(&mmc_devices);
//1.这句代码的含义是:初始化全局变量mmc_devices
//2.初始化的值是:将mmc_devices这个结构体类型的变量中的next、prev指针均指向自己
//3.mmc_devices的作用:是用来记录系统中所有已经注册的SD/iNand设备
//4.举例:向系统中插入一个SD卡/iNand设备,则系统驱动就会向mmc_devices链表中增加一个节点表示这个设备。
cur_dev_num = 0;
//cur_dev_num的作用:记录当前正在操作的SD卡设备的设备号
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;
}
(3)mmc_initialize这个函数的作用就是初始化板上的MMC系统。
MMC系统的初始化应该包含这么两部分:
SoC里的SD/MMC控制器初始化(MMC系统时钟的初始化、GPIO初始化、SFR初始化)
SD卡/iNand 内部控制器的初始化。
//SD卡有很多种状态,获取到不同的命令,就会执行不同的操作,然后进入下一种状态;
//在SD卡中有个小型控制器,封装了读写SD卡的函数。
//这里的初始化SD卡就是为了 初始化SD卡的状态到合适。
(4)mmc_devices链表(全局变量),用来记录系统中所有已经注册的SD/iNand设备。所以向系统中插入一个SD卡/iNand设备,则系统驱动就会向mmc_devices链表中插入一个数据结构表示这个设备。
2.1、cpu_mmc_init(mmc_initialize函数1191行处调用)
(1)函数定义在:uboot/cpu/s5pc11x/cpu.c中。实质是通过调用3个函数来完成的。
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
}
2.1.1、setup_hsmmc_clock
(1)函数定义在:uboot/cpu/s5pc11x/setup_hsmmc.c中。看名字函数是用来初始化SoC中MMC控制器的时钟部分的。
void setup_hsmmc_clock(void)
{
u32 tmp;
u32 clock;
u32 i;
/* MMC0 clock src = SCLKMPLL */
//CLK_SRC4低4位设置为0110,意思是设置MMC0通道的源时钟为SCLKMPLL
tmp = CLK_SRC4_REG & ~(0x0000000f);
CLK_SRC4_REG = tmp | 0x00000006;
/* MMC0 clock div */
tmp = CLK_DIV4_REG & ~(0x0000000f);//CLK_DIV4di4位清0
clock = get_MPLL_CLK()/1000000;//MPLL_CLK = 667MHz,clock = 667
for(i=0; i<0xf; i++)
{
if((clock / (i+1)) <= 50) { 667/14 < 50, i+1 = 14, i = 13 = (1101)
CLK_DIV4_REG = tmp | i<<0; //对MPLL_CLK(667M)进行14分频得到SCLKMPLL(47M)
break;
}
}
#ifdef USE_MMC2
/* MMC2 clock src = SCLKMPLL */
tmp = CLK_SRC4_REG & ~(0x00000f00);
CLK_SRC4_REG = tmp | 0x00000600;
/* MMC2 clock div */
tmp = CLK_DIV4_REG & ~(0x00000f00);
CLK_DIV4_REG = tmp | i<<8;
#endif
}
2.1.2、setup_hsmmc_cfg_gpio
(1)函数定义在:uboot/cpu/s5pc11x/setup_hsmmc.c中。看名字函数是用来配置SoC中MMC控制器相关的GPIO的。
2.1.3、smdk_s3c_hsmmc_init
(1)函数定义在:uboot/drivers/mmc/s3c_hsmmc.c中。函数内部通过宏定义USE_MMCx来决定是否调用s3c_hsmmc_initialize来进行具体的初始化操作。
int smdk_s3c_hsmmc_init(void)
{
int err;
#ifdef USE_MMC0 //x210使用这里
err = s3c_hsmmc_initialize(0);
if(err)
return err;
#endif
#ifdef USE_MMC1
err = s3c_hsmmc_initialize(1);
if(err)
return err;
#endif
#ifdef USE_MMC2 //x210使用这里
err = s3c_hsmmc_initialize(2);
if(err)
return err;
#endif
#ifdef USE_MMC3
err = s3c_hsmmc_initialize(3);
if(err)
return err;
#endif
return -1;
}
2.1.3.1、s3c_hsmmc_initialize
(1)函数定义在:uboot/drivers/mmc/s3c_hsmmc.c中。
static int s3c_hsmmc_initialize(int channel)
{
struct mmc *mmc;
//struct mmc封装了SD卡所有相关的信息和操作函数,和C++中使用类来面向对象很像。
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);
}
(2)定义一个指向struct mmc类型的指针,指向mmc_channel数组中的元素,mmc_channel数组中的每个元素都是struct mmc类型的对象,之后填充这个指针指向的对象的成员和成员函数,最后调用mmc_register函数来向驱动框架注册这个mmc设备驱动。
int mmc_register(struct mmc *mmc)
{
/* 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;
INIT_LIST_HEAD(&mmc->link);
list_add_tail(&mmc->link, &mmc_devices);//将该设备插入内核链表
return 0;
}
(3)mmc_register功能是进行mmc设备的注册,注册方法就是将当前这个mmc插入到mmc_devices这个内核链表中去。
(4)我们在x210_sd.h中定义了USE_MMC0和USE_MMC2,因此在我们的uboot初始化时会调用2次s3c_hsmmc_initialize函数,传递参数分别是0和2,因此完成之后系统中会注册上2个mmc设备,表示当前系统中有2个mmc通道在工作。
(5)至此cpu_mmc_init函数分析完成。完成了以下工作:
SoC里的MMC控制器初始化(MMC系统时钟的初始化、SFR初始化、GPIO初始化)
2.2、find_mmc_device
(1)函数定义在:uboot/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;
}
(2)这个函数的作用就是通过mmc设备号在内核链表中查找对应的mmc设备(struct mmc的对象,根据上面2.1.3.1.(4)分析系统中有2个mmc设备,编号分别是0和2)。函数工作原理就是遍历mmc_devices链表,去依次寻找注册了的mmc设备,然后对比设备编号,如果相同则就说明找到了设备。找到后调用mmc_init函数来初始化这个设备。
2.3、mmc_init
(1)函数定义在:drivers/mmc/mmc.c中。
(2)这个函数应该是要进行mmc卡的初始化了(前面已经完成了SoC端控制器的初始化)
(3)函数的调用关系为:
mmc_init //发送若干个命令码初始化SD卡内部的控制器
mmc_go_idle //命令1
mmc_send_cmd //发送命令1
mmc_send_if_cond //命令2
mmc_send_cmd //发送命令2
······
可以看出,mmc_init函数内部就是依次通过向mmc卡发送命令码(CMD0、CMD2那些)来初始化SD卡/iNand内部的控制器,以达到初始化SD卡的目的。
三、总结
(1)至此整个MMC系统初始化结束。
(2)整个MMC系统初始化分为2大部分:SoC这一端的MMC控制器的初始化,SD卡这一端卡本身的初始化。前一部分主要是在cpu_mmc_init函数中完成,后一部分主要是在mmc_init函数中完成。
(3)整个初始化完成后去使用sd卡/iNand时,操作方法和mmc_init函数中初始化SD卡的操作方式一样。读写sd卡时也是通过总线向SD卡发送命令、读取/写入数据来完成的。
(4)顺着mmc_init函数中发送命令的操作追下去,到了mmc_send_cmd函数处就断了,真正的向SD卡发送命令的硬件操作的函数找不到。
(5)struct mmc结构体是关键。两部分初始化之间用使用mmc结构体来联系,初始化完了后对mmc卡的常规读写操作也是通过mmc结构体来链接的。
四、linux驱动前奏
4.1、struct mmc
(1)驱动的设计中有一个关键数据结构,每个驱动都对应着一个数据结构。譬如MMC驱动的结构体就是struct mmc,这个结构体中包含一些变量和一些函数指针,变量用来记录SD卡相关的属性,函数指针用来记录SD卡相关的操作方法。这些变量和函数指针加起来就构成了驱动。驱动就被抽象为这个结构体。
(2)一个驱动工作时主要就分两部分(以iNand/SD驱动为例子):
驱动构建(构建一个struct mmc对象然后填充它)、驱动运行时(调用这个对象里的函数指针和变量)
4.2、分离思想—面向对象
(1)分离思想就是说在驱动中将操作方法和数据分开,然后封装在一个结构体中。
(2)操作方法就是函数,数据就是变量。所谓操作方法和数据分离的意思就是:在不同的地方来存储和管理驱动的操作方法和变量,这样的优势就是驱动便于移植。
4.3、分层思想
(1)分层思想是指一整个的驱动分为好多个层次。驱动分为很多个源文件,放在很多个文件夹中。
(2)以mmc驱动为例来分析各个文件的作用:
uboot/drivers/mmc/mmc.c:本文件的主要内容是和MMC卡操作有关的方法,譬如MMC卡设置复位状态的mmc_go_idle、卡读写数据等。但是本文件中并没有具体的硬件寄存器操作函数,操作最终指向的是struct mmc结构体中的函数指针send_cmd,这些函数指针是在驱动构建的时候和真正硬件操作函数挂勾的(真正的硬件操作函数在别的文件中)。
uboot/drivers/mmc/s3c_hsmmc.c:本文件中主要内容是SoC内部MMC控制器的硬件寄存器操作方法,真正的寄存器操作就在这里,譬如向SD卡发送命令的函数(s3c_hsmmc_send_command),譬如和SD卡读写数据的函数(s3c_hsmmc_set_ios),这些函数就是具体操作硬件的函数,也就是mmc.c中函数指针最终指向的函数。这些函数在mmc驱动初始化构建时(cpu_mmc_init-> smdk_s3c_hsmmc_init-> s3c_hsmmc_initialize函数中)和struct mmc对象挂勾起来备用。
分析:mmc.c和s3c_hsmmc.c构成了一个分层,mmc.c中调用了s3c_hsmmc.中的函数,所以mmc.c在上层,s3c_hsmmc.c在下层。这两个分层后我们发现mmc.c中不涉及具体硬件的操作,s3c_hsmmc.c中不涉及驱动工作时的时序操作(发送命令的先后顺序)。因此移植的时候就有好处:譬如我们要把这一套mmc驱动移植到别的SoC上,mmc.c就不用动,s3c_hsmmc.c动就可以了;譬如SoC没变但是SD卡升级了,这时候只需要更换mmc.c,不需要更换s3c_hsmmc.c即可。
cpu/s5pc110/下面还有一个setup_hsmmc.c,也和MMC驱动有关。但是这些代码为什么不能放到drivers目录下去,而要放到cpu目录下去?因为这里面的2个函数(setup_hsmmc_clock和setup_hsmmc_cfg_gpio)都是和SoC内部有关的初始化函数,这两个函数不能放到drivers目录下去。实际上如果非把这两个函数放在uboot/drivers/mmc/s3c_hsmmc.c文件中也能说过去。