软件架构
1)块设备层(card/)
存放闪存卡(块设备)的相关驱动,如MMC/SD卡设备驱动
按照 linux块设备驱动程序的框架实现一个mmc/SD卡的块设备驱动,这 block.c 当中我们可以看到写一个块设备驱动程序时需要的 block_device_operations 结构体变量的定义,其中有 open/release/request 函数的实现,而 queue.c 则是对内核提供的请求队列的封装。
2)核心层(core)
整个MMC的核心层,这部分完成不同协议和规范的实现,为host层和设备驱动层提供接口函数。
核心层封装了 MMC/SD 卡的命令(CMD),例如存储卡的识别,设置,读写。例如不管什么卡都应该有一些识别,设置,和读写的命令,这些流程都是必须要有的,只是具体对于不同的卡会有一些各自特有的操作。 core.c 文件是由 sd.c 、 mmc.c、sdio.c 三个文件支撑的, core.c 把 MMC 卡、 SD 卡和SDIO卡共性抽象出来,它们的差别由 sd.c 和 sd_ops.c 、 mmc.c 和 mmc_ops.c 、sdio.c和sdio_ops.c来完成。
core层通过操作host的ops接口访问控制器
set_ios方法的主要作用是设置SD卡的I/O状态,包括但不限于:
- 时钟频率:设置SD卡的工作时钟频率。
- 总线宽度:设置数据总线的宽度,例如4位或8位总线。
- 电源状态:配置SD卡的电源管理模式。
3)控制器层(host)
针对不同主机端的SDHC、MMC控制器的驱动,这部分需要由驱动工程师来完成;
主机控制器则是依赖于不同的平台的,硬件相关,需要针对不同芯片,不同芯片对应的控制器来实现对应的代码。一般情况要进行一些设置,例如中断函数注册,控制器寄存器初始化等等。然后它会向 core 层注册一个主机( host ),用结构 mmc_host_ops 描述,这样核心层就可以拿着这个 host 来操作对应的卡控制器了,而具体是什么控制器, core 层是不用知道的。
如下:
dw_mci_probe:将host控制器挂载在PCI总线上,通过调用dw_mci_probe,初始化host控制器
1:设置时钟频率
2:初始化化SDIO HOST控制器寄存器
3:注册中断回调函数
ret = devm_request_irq(host->dev, host->irq, dw_mci_interrupt,
host->irq_flags, "dw-mci", host);
4:dw_mci_init_slot中,申请mmc_host,并注册操作接口函数, dw_mci_ops这些操作接口函数用于访问不同厂商mmc_host寄存器;并将mmc_host加入到设备链表中ret = mmc_add_host(mmc);
SDIO特性描述
1:SDIO CARD支持SPI模式、SDIO 4线和1线模式。如下图:
2:SDIO最大支持8个function,包括function0和function1~function7
3:SDIO速率模式可以支持低速模式、高速模式、以及UHS模式。如下图:
4:COMBO CARD:组合卡指SDIO+存储器,组合卡中SDIO最高CLOCK为25MHZ,SD最高高于25M时强制4bitsSD模式.如下图
mmc_rescan扫卡的详细流程来介绍,详细讲解一下SDIO协议的探卡流程:
1:SDIO探卡首先使用400K时钟进行探卡,如果400K时钟探卡失败的情况下,会降频到300K、200K和100K时钟探卡。在device侧必须要等到host有时钟供给之后,才能初始化SDIO,否则在没有时钟的情况下初始化SDIO,SDIO寄存器会无法写入或导致总线挂死等异常情况。
2:Host SDIO控制器上电,并设置探卡时钟,如:开始使用400K时钟探卡,这个时候,就能看到clk pin有400K时钟输出。如下图:
3:在不知道当前是啥CARD类型的情况下,我们可以发送cmd52命令,地址按SDIO协议填写I/O ABORT(0x06),复位SDIO卡,重新初始化,在device收到SDIO reset命令之后,通过写SDIO寄存器复位SDIO,然后再重新初始化SDIO卡。SD/emmc不支持cmd52命令,将不响应此命令。
4:发送CMD0命令,让CARD进入到IDLE态,如果非SPI模式,将DATA0~2管脚拉高,data3管脚不会拉高,需要硬件工程师关注初始态。进入空闲态,如果是SPI模式,data3为片选管脚。
5:发送CMD8命令。此命令是新命令,用于区分SD卡0,只有0及以后的卡才支持CMD8命令,MMC卡和V1.x的卡,是不支持该命令的。设置VHS位,以告诉SD卡,主机的供电情况,让SD卡知道主机的供电范围。建议在“check pattem”中使用“10101010b”,这里我们使用参数0X1AA,即告诉SD卡,主机供电为2.7~3.6V之间,如果SD卡支持CMD8,且支持该电压范围,则会通过CMD8的响应(R7)将参数部分原本返回给主机,如果不支持CMD8,或者不支持这个电压范围,则不响应。
6:从这开始,我们就要开始探测SDIO卡了,最开始探测SDIO卡、SD卡,最后探测MMC卡。
7:发送CMD5命令,获取CARD支持的电压范围,并等待CARD初始化完成,每隔10ms获取一次,尝试100次超时,如果device一直不设置ready位,就会超时。在device侧需要设置好支持的电压,在初始化ready之后,设置相应的寄存器,标记为ready状态。
8: 设置mmc的bus层操作接口
mmc_attach_bus(host, &mmc_sdio_ops);
9: 根据host支持的电压范围和OCR寄存器中读取的电压范围,得出两者都支持的电压范围,然后选取最小的电压值。
10:发送CMD5命令,通知card设置电压的最小和最大的电压值。
11:如果是SPI模式,使能SPI的CRC数据检验。
12:申请struct mmc_card内存,并初始化设备device_initialize,设置card类型为SDIO CARD,总线类型为MMC BUS,后续用于添加到设备链表中。
13:检查Memory present位,如果置1,当前card里面也包括SD memory card,发送CMD2获取SD卡的CID信息,并设置card type为COMBO,否则设置为SDIO CARD.并设置card的OCR(Operation Conditions Register The supported minimum and maximum values for VDD)
14: 通过判断当前CARD的ORC是否支持1.8v VDD,判断当前是否是UHS模式,如果支持1.8v VDD,那么发送cmd11命令,通知device从1.8v切换到3.3v.
15: 发送CMD3,从31~16bit获取RCA值,并设置CARD的RCA. 用于设置卡相对地址(RCA,必须为非 0),对于 SD 卡(非 MMC 卡),在收到CMD3 后,将返回一个新的 RCA 给主机,方便主机寻址.
16: 如果CARD类型为combo card,发送CMD9读取SD卡CSD信息。CSD信息如下:
17:发送CMD7选CARD,选中要操作的SDIO CARD.
18: 发送CMD52命令,获取CCCR寄存器,了解SDIO CARD对SDIO协议的支持能力。获取SDIO_CCCR_CAPS,以及根据CCCR支持的协议模式,获取相应的速率模式,根据不同的速率模式,可以在用户自定义的的寄存器区间,设置一些用户定义操作,如设置IO驱动能力。如下图:
19:获取CARD的CIS信息,先获取Function0的FBR地址0x09~0x0b的CIS索引值,通过索引值,找到CIS存放的地址空间,CIS中包含一些CARD的基本信息,比如VID&PID、BLK SIZE大小等。目前最大能支持8个function。
20:如果当前CARD支持UHS模式,那么发送CMD7命令,使能4线模式,并发送CMD19,进行速率模式切换,切高频,并设置相应的时钟,最后进行相位校准(SDR50和SDR104才会进行调相)。
21:如果是HIGH SPEED模式,发CMD52 SDIO_CCCR_SPEED切换到HIGH SPEED模式,并切高频。
22:初始化所有的的functions.并将mmc_card设备加入到SDIO总线的设备链表中。
23:最后,总结一下,在探卡阶段,都只会用到clk 和cmd线。在探卡完成之后,我们需要使能相应的function,并设备card的blk size,这个时候,我们就可以通过cmd53,走data0~data3进行数据通信了。
24:在使用CMD53和device通信的时候,我们需要注册SDIO中断,用于接收device发送过来的数据,data0复用为中断信号线,device启动DMA,向host发送数据,data0拉低,低电平中断,其他data1~data3拉高,处于空闲态,这个时候触发host中断,进入中断下半部,先host发送CMD52命令,先清device中断,再通过CMD53读取数据,通过DMA将数据搬运到我们指定的内存。在DMA搬运完成之后,device会触发DMA搬运完成中断,那么一笔数据就搬运完成了。