linux sd卡驱动分析基于mini2440sdio mmc sd卡驱动编写Word文档下载推荐.docx
《linux sd卡驱动分析基于mini2440sdio mmc sd卡驱动编写Word文档下载推荐.docx》由会员分享,可在线阅读,更多相关《linux sd卡驱动分析基于mini2440sdio mmc sd卡驱动编写Word文档下载推荐.docx(14页珍藏版)》请在冰豆网上搜索。
Bluetoothcard
Radio/TVcard(很好玩)
SDIO的应用将是未来嵌入式系统最重要的接口技术之一,并且也会取代目前GPIO式的SPI接口。
SD/SDIO的传输模式
SD传输模式有以下3种:
SPImode(required)
1-bitmode
4-bitmode
SDIO同样也支持以上3种传输模式。
依据SD标准,所有的SD(记忆卡)与SDIO(外围)都必须支持SPImode,因此SPImode是「required」。
此外,早期的MMC卡(使用SPI传输)也能接到SD插糟(SDslot),并且使用SPImode或1-bitmode来读取。
SD的MMCMode
SD也能读取MMC内存,虽然MMC标准上提到,MMC内存不见得要支持SPImode(但是一定要支持1-bitmode),但是市面上能看到的MMC卡其实都有支持SPImode。
因此,我们可以把SD设定成SPImode的传输方式来读取MMC记忆卡。
SD的MMCMode就是用来读取MMC卡的一种传输模式。
不过,SD的MMCMode虽然也是使用SPImode,但其物理特性仍是有差异的:
MMC的SPImode最大传输速率为20Mbit/s;
SD的SPImode最大传输速率为25Mbit/s。
为避免混淆,有时也用SPI/MMCmode与SPI/SDmode的写法来做清楚区别。
2.MMC子系统的基本框架结构:
很遗憾,内核没有为我们提供关于MMC子系统的文档,在谷歌上搜索了很多,也没有找到相关文章。
只能自己看代码分析了,可能有很多理解不对的地方,希望研究过这方面的朋友多邮件交流一下。
MMC子系统的代码在kernel/driver/MMC下,目前的MMC子系统支持一些形式的记忆卡:
SD,SDIO,MMC.由于笔者对SDIO的规范不是很清楚,后面的分析中不会涉及。
MMC子系统范围三个部分:
HOST部分是针对不同主机的驱动程序,这一部是驱动程序工程师需要根据自己的特点平台来完成的。
CORE部分:
这是整个MMC的核心存,这部分完成了不同协议和规范的实现,并为HOST层的驱动提供了接口函数。
CARD部分:
因为这些记忆卡都是块设备,当然需要提供块设备的驱动程序,这部分就是实现了将你的SD卡如何实现为块设备的。
3.HOST层分析:
HOST层实现的就是我们针对特定主机的驱动程序,这里以mini2440的s3cmci.c为例子进行分析,我们先采用platform_driver_register(&
s3cmci_2440_driver)注册了一个平台设备,接下来重点关注probe函数。
在这个函数总,我们与CORE的联系是通过下面三句实现的。
首先分配一个mmc_host结构体,注意sizeof(structs3cmci_host),这样就能在mmc_host中找到了s3cmci_host,嵌入结构和被嵌入的结构体能够找到对方在Linux内核代码中的常用技术了。
接下来为mmc->
pos赋值,s3cmci_ops结构实现了几个很重要的函数,待会我一一介绍。
中间还对mmc结构的很多成员进行了赋值,最后将mmc结构加入到MMC子系统,mmc_alloc_host,以及mmc_add_host的具体做了什么事情,我们在下节再分析,这三句是些MMC层驱动必须包含的。
mmc=mmc_alloc_host(sizeof(structs3cmci_host),&
pdev->
dev);
mmc->
ops=&
s3cmci_ops;
……………
s3cmci_ops中包含了四个函数:
staticstructmmc_host_opss3cmci_ops={
.request=s3cmci_request,
.set_ios
=s3cmci_set_ios,
.get_ro
=s3cmci_get_ro,
.get_cd
=s3cmci_card_present,
};
我们从简单的开始分析,这些函数都会在core部分被调用:
s3cmci_get_ro:
这个函数通过从GPIO读取,来判断我们的卡是否是写保护的
s3cmci_card_present:
这个函数通过从GPIO读取来判断卡是否存在
s3cmci_set_ios:
s3cmci_set_ios(structmmc_host*mmc,structmmc_ios*ios)
依据核心层传递过来的ios,来设置硬件IO,包括引脚配置,使能时钟,和配置总线带宽。
s3cmci_request:
这个函数是最主要,也最复杂的函数,实现了命令和数据的发送和接收,
当CORE部分需要发送命令或者传输数据时,都会调用这个函数,并传递mrq请求。
staticvoids3cmci_request(structmmc_host*mmc,structmmc_request*mrq)
{
structs3cmci_host*host=mmc_priv(mmc);
host->
status="
mmcrequest"
;
cmd_is_stop=0;
mrq=mrq;
if(s3cmci_card_present(mmc)==0){
dbg(host,dbg_err,"
%s:
nomediumpresent\n"
__func__);
mrq->
cmd->
error=-ENOMEDIUM;
mmc_request_done(mmc,mrq);
//如果卡不存在,就终止请求
}else
s3cmci_send_request(mmc);
}
接下来看s3cmci_send_request(mmc):
这个函数先判断一下请求时传输数据还是命令,如果是数据的话:
先调用s3cmci_setup_data来对S3C2410_SDIDCON寄存器进行设置,然后设置SDITIMER寄存器这就设置好了总线宽度,是否使用DMA,,并启动了数据传输模式,并且使能了下面这些中断:
imsk=S3C2410_SDIIMSK_FIFOFAIL|S3C2410_SDIIMSK_DATACRC|
S3C2410_SDIIMSK_DATATIMEOUT|S3C2410_SDIIMSK_DATAFINISH;
解析来判断是否是采用DMA进行数据传输还是采用FIFO进行数据传输
if(host->
dodma)
/
becausehost->
dodma
=0,sowedon'
tuseit
res=s3cmci_prepare_dma(host,cmd->
data);
//准备DMA传输,
else
res=s3cmci_prepare_pio(host,cmd->
.//准备FIFO传输
如果是命令的话:
则调用s3cmci_send_command()这个函数是命令发送的函数,和datesheet上描述的过程差不多,关于SD规范中命令的格式,请参考参考资料1.
writel(cmd->
arg,host->
base+S3C2410_SDICMDARG);
/*先写参数寄存器
ccon=cmd->
opcode&
S3C2410_SDICMDCON_INDEX;
//确定命令种类
ccon|=S3C2410_SDICMDCON_SENDERHOST|S3C2410_SDICMDCON_CMDSTART;
/*withstart2bits*/
if(cmd->
flags&
MMC_RSP_PRESENT)
ccon|=S3C2410_SDICMDCON_WAITRSP;
/*waitrsp*/
MMC_RSP_136)
ccon|=S3C2410_SDICMDCON_LONGRSP;
//确定respose的种类
writel(ccon,host->
base+S3C2410_SDICMDCON);
命令通道分析完了,我们分析数据通道,先分析采用FIFO方式传输是怎么样实现的。
先分析s3cmci_prepare_pio(host,cmd->
data)
根据rw来判断是读还是写
if(rw){
do_pio_write(host);
/*DeterminesSDIgenerateaninterruptifTxFIFOfillshalf*/
enable_imask(host,S3C2410_SDIIMSK_TXFIFOHALF);
}else{
enable_imask(host,S3C2410_SDIIMSK_RXFIFOHALF
|S3C2410_SDIIMSK_RXFIFOLAST);
}
如果是写数据到SD的话,会调用do_pio_write,往FIFO中填充数据。
当64字节的FIFO少于33字节时就会产生中断。
如果是从SD读数据,则先使能中断,当FIFO多于31字节时时,则会调用中断服务程序,中断服务程序中将会调用do_pio_readFIFO的数据读出。
接下来分析do_pio_write:
to_ptr=host->
base+host->
sdidata;
fifo_free(host)用来检测fifo剩余空间
while((fifo=fifo_free(host))>
3){
if(!
host->
pio_bytes){
res=get_data_buffer(host,&
pio_bytes,
/*Ifwehavereachedtheendoftheblock,wehaveto
*writeexactlytheremainingnumberofbytes.Ifwe
*inthemiddleoftheblock,wehavetowritefull
*words,sorounddowntoanevenmultipleof4.*/
if(fifo>
=host->
pio_bytes)//fifo的空间比pio_bytes大,表明这是读这个块的最后一次
fifo=host->
pio_bytes;
/*becausethevolumeofFIFOcancontaintheremaningblock*/
fifo-=fifo&
3;
/*rounddowntoanevenmultipleof4*/
pio_bytes-=fifo;
//更新还剩余的没有写完的字
pio_count+=fifo;
/*changthevalueofpio_bytes*/
fifo=(fifo+3)>
>
2;
//将字节数转化为字数
/*howmanywordsfifocontain,everytimewejustwritoneword*/
ptr=host->
pio_ptr;
while(fifo--)
writel(*ptr++,to_ptr);
//写往FIFO.
pio_ptr=ptr;
注释一:
注意,MMC核心为mrq->
data成员分配了一个structscatterlist的表,用来支持分散聚集,使用这种方法,这样使物理上不一致的内存页,被组装成一个连续的数组,避免了分配大的缓冲区的问题
我们看代码
if(host->
pio_sgptr>
data->
sg_len){
dbg(host,dbg_debug,"
nomorebuffers(%i/%i)\n"
pio_sgptr,host->
sg_len);
return-EBUSY;
sg=&
sg[host->
pio_sgptr];
*bytes=sg->
length;
//页缓冲区中的长度
*pointer=sg_virt(sg);
将页地址映射为虚拟地址
pio_sgptr++;
这里表明我们的程序又完成了一次映射
这样,每一个mmc请求,我们只能处理scatterlist表中的一个页(块)。
因此,完成一次完整的请求需要映射sg_len次
再来总结一下一个mmc写设备请求的过程:
在s3cmci_prepare_pio中我们第一次先调用do_pio_write,如果FIFO空间大于3,且能够获取到scatterlist,则我们就开始往FIFO写数据,当FIFO空间小于3,则使能TXFIFOHALF中断,在中断服务程序中,如果检测到TFDET表明又有FIFO空间了,则关闭TXFIFOHALF中断,并调用do_pio_write进行写。
数据流向如下:
scatterlist-------->
fifo---------->
sdcard
一个mmc读设备请求的过程数据流向如下:
sdcard-------->
fifo---------->
scatterlist,
?
关于读数据的过程,中断的触发不是很清楚,s3cmci_prepare_pio中enable_imask(host,S3C2410_SDIIMSK_RXFIFOHALF,S3C2410_SDIIMSK_RXFIFOLAST);
但如果没从SD卡中读数据,怎么会引发这个中断呢?
是由S3C2410_SDIIMSK_RXFIFOLAST引起的吗
接下来我们分析一下中断服务程序:
staticirqreturn_ts3cmci_irq(intirq,void*dev_id)
该程序先获取所有的状态寄存器:
mci_csta=readl(host->
base+S3C2410_SDICMDSTAT);
mci_dsta=readl(host->
base+S3C2410_SDIDSTA);
mci_dcnt=readl(host->
base+S3C2410_SDIDCNT);
mci_fsta=readl(host->
base+S3C2410_SDIFSTA);
mci_imsk=readl(host->
sdiimsk);
这些将作为中断处理的依据。
如果不是DMA模式,则处理数据的收发
if(!
dodma){
if((host->
pio_active==XFER_WRITE)&
&
(mci_fsta&
S3C2410_SDIFSTA_TFDET)){
/*ThisbitindicatesthatFIFOdataisavailablefortransmitwhen
DatModeisdatatransmitmode.IfDMAmodeisenable,sd
hostrequestsDMAoperation.*/
disable_imask(host,S3C2410_SDIIMSK_TXFIFOHALF);
tasklet_schedule(&
pio_tasklet);
注意我们采用tasklet这种延时机制来减少中断服务的时间,延时函数pio_tasklet中调用了do_pio_write和了do_pio_read
piotx"
pio_active==XFER_READ)&
S3C2410_SDIFSTA_RFDET)){
disable_imask(host,
S3C2410_SDIIMSK_RXFIFOHALF|
S3C2410_SDIIMSK_RXFIFOLAST);