uboot之硬件驱动部分

一、uboot和Linux驱动

1、uboot本身是裸机程序

裸机程序中是直接控制硬件的,但是操作系统中必须通过驱动来控制硬件。这2个的本质区别就是分层结构,因为在操作系统中需要按照操作系统的框架来部署驱动,也就是操作硬件的代码,按照操作系统规则来写代码。但是在裸机中没有这样的概念,分层的结构可以按照自己的思路,自己想怎么做就怎么做,所以uboot中的驱动和Linux中驱动是不同的。但是源代码确实相同的,组织结构不同罢了。

2、uboot的虚拟地址对硬件操作的影响

(1)操作系统Linux中MMU肯定是开启的,也就是说在Linux操作系统中所使用的地址都是虚拟地址,所以驱动中使用的也是虚拟地址。所以在移植Linux驱动的时候,就需要考虑到虚拟地址的问题。uboot中开启了MMU,但是只有0x30000000~0x3FFFFFFF的地址映射到了0xC0000000~0xCFFFFFFF处,其余的地址都是原样映射的,所以我们的SFR所在的物理地址为0xExxxxxxx开头,不会受到影响的,直接使用对应的物理地址。不用考虑虚拟地址的问题。

3、uboot移植了Linux驱动

(1)Linux驱动本身就是模块化设计的,Linux驱动和Linux内核不是强耦合的,这样的涉及也就是为了程序员能够很好的移植驱动,这也是Linux可移植的关键所在。

(2)uboot移植了Linux驱动的源代码,uboot是从源代码级别去移植Linux驱动,这就是Linux系统的开源性。

(3)uboot的硬件驱动比Linux的简单,Linux驱动本身有更复杂的框架,需要实现更多的附带功能,而uboot实际是一个裸机程序,uboot只移植Linux驱动的一部分代码。

 

二、iNand和SD驱动解析

1、从start_armboot开始

(1)驱动庞大,涉及到很多的文件。

(2)我们从mmc_initialize(gd->bd)函数开始分析,驱动代码。

 

2、mmc_initialize(gd->bd):uboot/drivers/mmc/mmc.c

(1)mmc的初始化

  • SOC里的MMC控制器的初始化:时钟初始化、SFR初始化、GPIO的初始化
  • SD卡/iNand芯片本身的初始化

其实这里的初始化就分成两个部分,一个是SOC内部的初始化,一个是SOC外部SD卡本身的初始化。其实这就是一种模式,几乎所用的外设都是这样的。首先把SOC内部的寄存器、GPIO、时钟都设置好了,然后SOC通过一定的接口和外设进行数据交互,对外设进行初始化。主体还是SOC本身。

(2)一个很重要的全局变量:mmc_devices,这个变量记录所有已经注册的SD卡/iNand设备。

(3)还有一个很重要的结构体struct mmc,这个在后面会详细介绍。

 

3、int cpu_mmc_init(bd_t *bis)函数:/uboot/cpu/s5pc11x/cpu.c

(1)在这个函数中,主要调用了三个函数

  •     setup_hsmmc_clock();
  •     setup_hsmmc_cfg_gpio();
  •     smdk_s3c_hsmmc_init();

(2)setup_hsmmc_clock():/uboot/cpu/s5pc11x/setup_hsmmc.c

  • 初始化了soc中的mmc时钟。

(3)setup_hsmmc_cfg_gpio():/uboot/cpu/s5pc11x/setup_hsmmc.c

  • 初始化mmc所需要的GPIO。

 

4、smdk_s3c_hsmmc_init函数:/uboot/drivers/s3c_hsmmc.c函数

(1)struct mmc结构体

struct mmc {
	struct list_head link;
	char name[32];
	void *priv;
	uint voltages;
	uint version;
	uint f_min;
	uint f_max;
	int high_capacity;
	uint bus_width;
	uint clock;
	uint card_caps;
	uint host_caps;
	uint ocr;
	uint scr[2];
	uint csd[4];
	uint cid[4];
	ushort rca;
	uint tran_speed;
	uint read_bl_len;
	uint write_bl_len;
	u32 capacity;
	struct mmc_ext_csd	ext_csd;	/* mmc v4 extended card specific */
	block_dev_desc_t block_dev;
	int (*send_cmd)(struct mmc *mmc,
			struct mmc_cmd *cmd, struct mmc_data *data);
	void (*set_ios)(struct mmc *mmc);
	int (*init)(struct mmc *mmc);
}

(2)我们可以看到这个结构体中有很多的变量,也有函数指针,s3c_hsmmc_initialize函数的作用就是对这个结构体进行封装,和我们前面所说的uboot命令实现方式是相同的,一个命令用一个结构体来表示,这里一个SD卡设备同样使用一个结构体来表示,不过这里的结构体更加复杂,我们可以用高级语言的思想来理解结构体,这里的struct mmc就相当于一个SD卡的类,一个设备就代表了一个对象,所以系统中有几个这样的设备,我们就需要实例化几个这样的对象。这个类中有成员变量,和成员函数,实例化一个对象就需要对这些成员函数和成员变量进行填充,也就是赋值。

static int s3c_hsmmc_initialize(int channel)
{
	struct mmc *mmc;
	mmc = &mmc_channel[channel];  //这里初始化了struct mmc结构体的数组
	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);
}

函数就是对MMC结构体进行填充,函数最后调用了一个mmc_register函数进行注册,

(3)mmc_register函数:

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;
}

最后两个句代码:

INIT_LIST_HEAD(&mmc->link);
list_add_tail(&mmc->link, &mmc_devices);

就是将当前设备添加到mmc_devices这个链表中,完成了注册。

 

5、find_mmc_device函数:/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) {  //遍历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;
}

找到了设备编号相同的设备,返回这个设备所对应的结构体。

 

6、mmc_init函数:/uboot/drivers/mmc/mmc.c中

(1)前面我们已经对SOC内部的所用东西进行了初始化,比如我们时钟,GPIO,进行了MMC设备的注册,这里的初始化都是涉及SD卡本身的初始化。

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);
}

通过SOC向SD卡发送特定的命令,初始化SD卡。

我们这里的函数结构:

mmc_go_idle--->mmc_send_cmd--->(mmc->send_cmd),我们可以看到最后函数回到了mmc这个结构体中,那么这个结构体中对应操作硬件的函数,这个函数是在我们封装mmc结构体的时候,赋值的。所以我们要回到对应的赋值函数中,才能找到正确的执行函数。在s3c_hsmmc_initialize函数中通过这句代码:

mmc->send_cmd = s3c_hsmmc_send_command;

所以最终调用的是static int s3c_hsmmc_send_command(struct mmc *mmc, struct mmc_cmd *cmd,struct mmc_data *data)这个函数哈。可以看到这个struct mmc结构体的关键,Linux驱动中也是这么实现的。

 

三、总结与思考

1、分离的思想

分离的思想就是说在驱动中,将操作方法和数据分开,也就是函数和变量分开,就是类中的成员变量和成员函数。在不同的地方存储和管理驱动的操作方法和变量,这样的优势就是便于移植。

2、分层的思想

(1)一个整个的驱动分为好多个层次,驱动分成好多个源程序。

(2)比如我们的mmc.c和s3c_hsmmc.c

mmc.c:本文件的主要内容和mmc卡操作方法有关;但是本文件没有具体的硬件操作,操作最终都是指向struct mmc中函数指针,这些函数指针是在驱动构建的时候和真正的函数连接的。

s3c_hsmmc.c:s5pv210内部mmc控制器、硬件的操作方法,涉及到是mmc的具体硬件。

mmc.c中不涉及具体的硬件操作,s3c_hsmmc.c中不涉及驱动工程时序操作。因此移植的时候,就很有好处:譬如我们要把一套mmc驱动以知道别的soc上面,mmc.c就需要动,需要改变的是s3c_hsmmc.c文件即可。譬如说soc没有变,但是SD卡升级了,我们这时候只需要改动mmc.c文件即可。

我还不能够很好的理解分离的思想。不过分层的思想很有用,移植的时候感觉很好。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值