一.最先来理清一下SD、MMC与SDIO的一些基本概念:
(1) MMC:(Multi Media Card)是较早的一种记忆卡标准,目前已经被 SD 标准所取代。
(2) SD:(Secure Digital Memory Card) 是一种 flash memory card 的标准,也就是一般常见的 SD 记卡,已完全兼容MMC标准。
(3) SDIO:(Secure Digital Input and Output Card)安全数字输入输出卡。SDIO是在SD标准上定义了一种外设接口,通过SD的I/O接脚来连接外围设备,并且通过SD上的 I/O数据接位与这些外围设备进行数据传输。是目前较热门的技术,目前有很多WIFI、GPS、Bluetooth、radio等模块都有了SDIO接口,也可以被称为“SDIO卡”。
(4) 接口的工作模式:工作模式是对CPU的控制器来说的,也就是说,CPU可以能过设置一些寄存器来将自身控制器设成置满足外挂设备要求的一种工作方式,即:SDIO工作模式、传统的SD模式以及MMC工作模式。这几种模式我的理解就是对应上面1-3三点的不同的外接设备。因为这三种设备的传输协议是有区别的。兼容关系是:“SDIO卡”> “SD卡”> “MMC卡”,反过来刚不成立。
(5) 接口的传输方式:传输方式也是针对CPU的控制器来讲的,一般的CPU控制器都会支持三种传输模式,即:SPI模式、1线模式、4线模式。那么如何才可以保证CPU工作在我们要求的模式以及传输方式呢?就是通过写CPU的控制寄存器来完成的。比如下面会提到的函数msmsdcc_set_ios(struct mmc_host *mmc,struct mmc_ios *ios).
(6) MMC/SD卡初始化时的指令以及各种状态:在网上找到以下的图片,可以很清楚的反映出SD卡初始化的指令,后面介绍代码时所发送的指令其实也是按照这个顺序来发送的:
二.Android(LINUX)下SD卡驱动的结构层次
我们知道,LINUX的驱动很喜欢分层,就像IIC以及输入子系统等驱动一样,LINUX下的SD卡驱动也是分了层次的,在我们的代码的kernel/drivers/mmc目录下包含了三个子目录,分别是:host、card、core三个部分:
HOST部分是针对不同主机的驱动程序,这一部是驱动程序工程师需要根据自己的特点平台来完成的,不过高通发布的CODEBASE里已经帮我们把这部分做好了。
CORE 部分: 这是整个MMC 的核心存,这部分完成了不同协议和规范的实现,并为HOST 层的驱动提供了接口函数。
CARD部分:因为这些记忆卡都是块设备,当然需要提供块设备的驱动程序,这部分就是实现了将你的SD 卡如何实现为块设备的。整个SD卡驱动的调用关系可以用如下的图来表示:
SD卡的一些基本概念这里就讲完了,下一节就主要开始分析代码喽。
三.下面分析一下高通的android2.3的代码中SD卡驱动的流程。
在kernel中,SD卡是作为平台设备加入到内核中去的,在/kernel/arch/arm/mach-msm/devices-msm7627a.c中:
static void __init msm7x2x_init(void)
-> static void __init msm7x27a_init_mmc(void)
-> msm_add_sdcc(1, &sdc1_plat_data); //devices-msm7627a.c中,其中sdc1_plat_data结构体中定义了平台相关的一些资源,比如检测脚号等,当然我们这里没有定义检测脚号。
-> pdev = msm_sdcc_devices[controller-1];
pdev->dev.platform_data = plat;
return platform_device_register(pdev); //在这里会向内核的platform bus总线上挂载SD卡的平台设备
同时KERNEL启动的过程中在kernel/drivers/mmc/core/core.c文件内会调用static int __init mmc_init(void)函数:
static int __init mmc_init(void)
-> ret = mmc_register_bus();//向系统中注册MMC BUS
ret = mmc_register_host_class();//向系统中添加mmc_host这个class类
ret = sdio_register_bus();//向系统中注册SDIO BUS,我们所使用的SD卡没有使用这个SDIO BUS,至于原因我还不清楚,有待继续学习。
在/kernel/drivers/mmc/card/block.c中调用:
static int __init mmc_blk_init(void)
->res=register_blkdev(MMC_BLOCK_MAJOR,"mmc"); //申请块设备号
res = mmc_register_driver(&mmc_driver); //向系统中注册mmc_driver,会msm_sdcc.c中注册的mmc_device相匹配后会调probe函数static int mmc_blk_probe(struct mmc_card *card)来申请一个块设备文件,最终SD卡所有的驱动都是通过这个块设备文件来调用的,也就是说,这个块设备文件是KERNEL与上层的接口。
在kernel/drivers/mmc/host/msm_sdcc.c文件中调用static int__init msmsdcc_init(void),向内核中注册struct platform_driver msmsdcc_driver,这个platform_driver,与之前注册的platform_devices相匹配后调用probe函数:
static int msmsdcc_probe(struct platform_device *pdev)
-> struct mmc_platform_data *plat = pdev->dev.platform_data
for (i = 0; i < pdev->num_resources; i++) //获取平台相关信息
mmc=mmc_alloc_host(sizeof(struct msmsdcc_host), &pdev->dev); //分配mmc_host结构体,同时嵌入msmsdcc_host结构体,这样方便以后由这两个结构体互相找到对方。mmc_host这个结构体是串联整个SD卡驱动的核心,需要特别注意。同时mmc_alloc_host这个函数很重要,稍后再来分析。
host = mmc_priv(mmc);
host->mmc = mmc; //这里就是在两个结构体中相互转换
/* ……. */ 中间这段代码主要是从内核空间到用户空间的映射,以及设置一些时钟
mmc->ops =&msmsdcc_ops; //设置mmc_host的操作函数
mmc->caps|= plat->mmc_bus_width;
ret =request_irq(core_irqres->start, msmsdcc_irq, IRQF_SHARED, DRIVER_NAME "(cmd)", host); //申请中断,我们由于没有使用中断脚,所以这里不会调用
mmc_add_host(mmc);//向系统中添加mmc_host设备,稍后再分析这个函数
然后我们来分析刚才提到的mmc_alloc_host函数:
struct mmc_host *mmc_alloc_host(int extra, structdevice *dev)
-> struct mmc_host*host;
host =kzalloc(sizeof(struct mmc_host) + extra, GFP_KERNEL);//这里要注意,我们观察mmc_host结构体会发现结构体的最后一个成员unsigned long private[0] ____cacheline_aligned 没有实际意思,仅表示一个地址,而我们正是利用这一点,将mmc_host结构体与上面传进来的msmsdcc_host结构体联系起来的。
dev_set_name(&host->class_dev,"mmc%d", host->index);
host->parent= dev;
host->class_dev.parent= dev;
host->class_dev.class= &mmc_host_class; //这一段会在sys/class/mmc_host/下建立mmc%d的一个文件
INIT_DELAYED_WORK(&host->detect,mmc_rescan);//初始化一个工作队列,延时函数为mmc_rescan,这个函数非常重要,后面再分析。
host->max_segs= 1;
host->max_seg_size= PAGE_CACHE_SIZE; //后面这些代码是对host进行一个默认的设置,有些设置可能会被上文中的probe函数替换掉
还记得之前在msm_sdcc.c中的msmsdcc_probe函数快结束的时候有mmc_add_host(mmc)这样一个函数吗?下面我们就来分析这个函数:
好了,刚才说了很多的mmc_rescan函数也等不及了,快来一睹它的真容吧:
void mmc_rescan(struct work_struct *work)
-> struct mmc_host *host = container_of(work, struct mmc_host, detect.work);//这个函数在LINUX里经常会用到,是内核黑客为了方便我们由一个结构体里一个成员的指针来得到整个结构体的指针所写的一个函数,非常有用
if (host->bus_ops && host->bus_ops->detect && !host->bus_dead && !(host->caps & MMC_CAP_NONREMOVABLE)) //这段是在系统唤醒的时候(由于在开机时已经设置了bus_ops)才会调用,所以KERNEL刚起的时候不会调用
mmc_rescan_try_freq(host, host->f_min)
-> mmc_power_up(host);//与上文分析的power_down类似
sdio_reset(host)//好像是让SDIO总线回到初始状态,我不是太清楚
mmc_go_idle(host)
-> cmd.opcode = MMC_GO_IDLE_STATE//即发送CMD0使SD卡处于IDLE状态
mmc_send_if_cond(host, host->ocr_avail)
-> cmd.opcode = SD_SEND_IF_COND//这个命令是专门为SD2.0协议的设备初始化使用的,只有SD2.0才支持这个命令
if (!mmc_attach_sdio(host))
if (!mmc_attach_sd(host))
if (!mmc_attach_mmc(host)) //这里依次判断外设是SDIO卡,SD卡或MMC卡中的哪一种,由于三种设备的协议会有一些差别,所以判断出来是哪一种设备后才好调用相应的操作函数
下面以mmc_attach_sd为例来分析:
int mmc_attach_sd(struct mmc_host *host)
-> err = mmc_send_app_op_cond(host, 0, &ocr) //发送CMD41来获取SD卡所支持的电压范围
mmc_sd_attach_bus_ops(host)
-> bus_ops = &mmc_sd_ops //设置host->bus_ops
mmc_sd_init_card(host, host->ocr, NULL)
-> err = mmc_sd_get_cid(host, ocr, cid, &rocr)
-> err = mmc_all_send_cid(host, cid) //发送CMD2以获取卡的身份信息
card = mmc_alloc_card(host, &sd_type) //分配一张SD结构的card
mmc_send_relative_addr(host, &card->rca)//获取卡的相对地址
err = mmc_sd_get_csd(host, card) //获取卡的寄存器的信息,包括 block 长度,卡容量等信息
err = mmc_select_card(card) //发送 CMD7, 选中目前 RADD 地址上的卡,任何时候总线上只有一张卡被选中,进入了传输状态
err = mmc_sd_setup_card(host, card, oldcard != NULL)
-> err = mmc_app_send_scr(card, card->raw_scr)
if (host->ops->get_ro) //判断是否有写保护,若有设置为只读卡
err = mmc_app_set_bus_width(card, MMC_BUS_WIDTH_4) //设置传输模式的总线宽度
到此为止,就完成了整个SD卡起动初始化的过程,在启动初始化完成之后,以后系统要调用SD卡的相动,都会通过之前注册的块设备来一步步的向下调用。
http://blog.csdn.net/dacaozuo/article/details/8163777