zoukankan      html  css  js  c++  java
  • Linux SD/MMC/SDIO驱动分析

    一、SD/MMC/SDIO概念区分

    SD(SecureDigital)与 MMC(MultimediaCard)

    SD 是一种 flash memory card 的标准,也就是一般常见的 SD 记忆卡,而 MMC 则是较早的一种记忆卡标准,目前已经被 SD 标准所取代。在维基百科上有相当详细的 SD/MMC 规格说明:[http://zh.wikipedia.org/wiki/Secure_Digital]。

    SDIO(SecureDigital I/O)

    SDIO 是目前我们比较关心的技术,SDIO 故名思义,就是 SD 的 I/O 接口(interface)的意思,不过这样解释可能还有点抽像。更具体的说明,SD 本来是记忆卡的标准,但是现在也可以把 SD 拿来插上一些外围接口使用,这样的技术便是 SDIO。

    所以 SDIO 本身是一种相当单纯的技术,透过 SD 的 I/O 接脚来连接外部外围,并且透过 SD 上的 I/O 数据接位与这些外围传输数据,而且 SD 协会会员也推出很完整的 SDIO stack 驱动程序,使得 SDIO 外围(我们称为 SDIO 卡)的开发与应用变得相当热门。

    现在已经有非常多的手机或是手持装置都支持 SDIO 的功能(SD 标准原本就是针对 mobile device 而制定),而且许多 SDIO 外围也都被开发出来,让手机外接外围更加容易,并且开发上更有弹性(不需要内建外围)。目前常见的 SDIO 外围(SDIO 卡)有:

      · Wi-Fi card(无线网络卡) 

      · CMOS sensor card(照相模块) 

      · GPS card 

      · GSM/GPRS modem card 

      · Bluetooth card 

      ·  Radio/TV card(很好玩)

    SDIO 的应用将是未来嵌入式系统最重要的接口技术之一,并且也会取代目前 GPIO 式的 SPI 接口。

    SD/SDIO 的传输模式

    SD 传输模式有以下 3 种:

      · SPI mode(required) 

      · 1-bit mode 

      ·  4-bit mode

    SDIO 同样也支持以上 3 种传输模式。依据 SD 标准,所有的 SD(记忆卡)与 SDIO(外围)都必须支持 SPI mode,因此 SPI mode 是「required」。此外,早期的 MMC 卡(使用 SPI 传输)也能接到 SD 插糟(SD slot),并且使用 SPI mode 或 1-bit mode 来读取。

    Secure digital I/Ocard,pin out

    SD 的 MMCMode

    SD 也能读取 MMC 内存,虽然 MMC 标准上提到,MMC 内存不见得要支持 SPI mode(但是一定要支持 1-bit mode),但是市面上能看到的 MMC 卡其实都有支持 SPI mode。因此,我们可以把 SD 设定成 SPI mode 的传输方式来读取 MMC 记忆卡。

    SD 的 MMC Mode 就是用来读取 MMC 卡的一种传输模式。不过,SD 的 MMC Mode 虽然也是使用 SPI mode,但其物理特性仍是有差异的:

      · MMC 的 SPI mode 最大传输速率为 20 Mbit/s; 

      · SD 的 SPI mode 最大传输速率为 25 Mbit/s。

    为避免混淆,有时也用 SPI/MMC mode 与 SPI/SD mode 的写法来做清楚区别

    参考网站:https://www.sdcard.org/developers/overview/capacity/

                  http://www.interfacebus.com/Secure_Digital_IO_Card_Pinout.html

     

    二、MMC子系统介绍

    MMC代码分布

    MMC子系统代码主要在drivers/mmc目录下,共有三个目录:

             Card:存放闪存卡(块设备)的相关驱动,如MMC/SD卡设备驱动,SDIOUART;

             Host:针对不同主机端的SDHC、MMC控制器的驱动,这部分需要由驱动工程师来完成;

             Core:整个MMC的核心层,这部分完成不同协议和规范的实现,为host层和设备驱动层提供接口函数。

    MMC子系统框架

     

    Linux MMC子系统主要分成三个部分:

      MMC核心层:完成不同协议和规范的实现,为host层和设备驱动层提供接口函数。MMC核心层由三个部分组成:MMC,SD和SDIO,分别为三类设备驱动提供接口函数;

      Host 驱动层:针对不同主机端的SDHC、MMC控制器的驱动;

      Client 驱动层:针对不同客户端的设备驱动程序。如SD卡、T-flash卡、SDIO接口的GPS和wi-fi等设备驱动。

     

    三、SD 总线协议

    SD总线通信是基于指令和数据比特流,起始位开始和停止位结束。SD总线通信有三个元素:

      Command:由host发送到卡设备,使用CMD线发送;

      Response:从card端发送到host端,作为对前一个CMD的相应,通过CMD线发送;

      Data:即能从host传输到card,也能从card传输到host,通过data线传输。

    Commands

    以下是四种用于控制卡设备的指令类型,每个command都是固定的48位长度:

      1、broadcast commands(bc), no response:广播类型的指令,不需要有响应;

      2、broadcast commands with response(bcr):广播类型的指令且需要响应;

      3、addressed(point-to-point) commands(ac):由HOST发送到指定的卡设备,没有数据的传输;

      4、address(point-to-point) data transfercommands(adtc):由HOST发送到指定的卡设备且伴随有数据传输。

    指令格式:

    Card register

    几个主要的寄存器:OCR,CID,CSD,RCA和SCR。

      Operation condition register(OCR):32位的OCR包含卡设备支持的工作电压表;

      Card identification number register (CID):包含用于在卡识别阶段的卡信息,包括制造商ID,产品名等;

      Card specific data register(CSD):CSD寄存器提供了如何访问卡设备的信息,包括定义了数据格式,错误校验类型,最大访问次数,数据传输率等;

      Relative card address register(RCA):存放在卡识别阶段分配的相对卡地址,缺省相对卡地址为0000h;

      SD card configuration register(SCR):SCR是一个配置寄存器,用于配置SD memory card的特殊功能。

    Response

    所有的response都通过CMD线发送到host端,R4和R5响应类型是SDIO中特有的:

      1、R1(normal response command):用来响应常用指令;

      2、R2(CID,CSD register):用来响应CMD2和CMD10或CMD9,并把CID或CSD寄存器作为响应数据;

      3、R3(OCR register):用来响应ACMD41指令,并把OCR寄存器作为响应数据;

      4、R6(published RCA response):分配相对卡地址的响应;

      5、R7(card interface condition):响应CMD8,返回卡支持的电压信息;

      6、R4(CMD5):响应CMD5,并把OCR寄存器作为响应数据;

      7、R5(CMD52):CMD52是一个读写寄存器的指令,R5用于CMD52的响应;

    Response 格式:

    ***详情请参考spec***

     

     

    四、SD初始化流程

    当host上电后,使所有的卡设备处于卡识别模式,完成设置有效操作电压范围,卡识别和请求卡相对地址等操作。

      1、发送指令CMD0使卡设备处于idle状态;

      2、发送指令CMD8,如果卡设备有response,说明此卡为SD2.0以上;

      3、发送指令CMD55+ACMD41,该指令是用来探测卡设备的工作电压是否符合host端的要求;

    在发送ACMD41这类指令之前需要先发送CMD55指令,在SDIO中ACMD41指令被CMD5替代。

      4、发送指令CMD11转换工作电压到1.8V;

      5、发送指令CMD2获取CIA;

      6、发送指令CMD3获取RCA(relative card address)

    SD初始化分析

    系统上电时,SDI控制器会去扫描总线上的所有设备,然后对挂在总线上卡设备进行初始化。进行扫描和初始化工作都是由mmc_scan函数来完成,以下是Linux驱动中初始化流程图(感谢同事Linkin的图)。

    SDIO、SD和MMC这三者的初始化流程稍有不同,是向下兼容的。

    转载:http://blog.csdn.net/paul_liao/article/details/7685869

     

    五、SD卡调试关键点:

      1. 上电时要延时足够长的时间给 SD 卡一个准备过程,在我的程序里是 5 秒,根据不同的卡设置不同的延时时间。 SD 卡初始化第一步在发送 CMD 命令之前,在片选有效的情况下首先要发送至少 74 个时钟,否则将有可能出现 SD 卡不能初始化的问题。

      2. SD 卡发送复位命令 CMD0 后,要发送版本查询命令 CMD8 ,返回状态一般分两种,若返回 0x01 表示此 SD 卡接受 CMD8, 也就是说此 SD 卡支持版本 2 ;若返回 0x05 则表示此 SD 卡支持版本 1 。因为不同版本的 SD 卡操作要求有不一样的地方,所以务必查询 SD 卡的版本号,否则也会出现 SD 卡无法正常工作的问题。

      3. 理论上要求发送 CMD58 获得 SD 卡电压参数,但实际过程中由于事先都知道了 SD 卡的工作电压,因此可省略这一步简化程序。协议书上也建议尽量不要用这个命令。

      4. SD 卡读写超时时间要按照协议说明书书上的给定值 ( 读超时: 100ms ;写超时: 250ms) ,这个值要在程序中准确计算出来,否则将会出现不能正常读写数据的问题。我自己定义了一个计算公式:超时时间 =( 8/clk )*arg 。

      5. 2GB 以内的 SD 卡 ( 标准卡 ) 和 2GB 以上的 SD 卡 ( 大容量卡 ) 在地址访问形式上不同,这一点尤其要注意,否则将会出现无法读写数据的问题。如标准卡在读写操作时,对读或写命令令牌当中的地址域符初值 0x10 ,表示对第 16 个字节以后的地址单元进行操作 ( 前提是此 SD 卡支持偏移读写操作 ) ,而对大容量卡读或写命令令牌当中的地址域符初值 0x10 时,则表示对第 16 块进行读写操作,而且大容量卡只支持块读写操作,块大小固定为 512 字节,对其进行字节操作将会出错。

      6. 对某一块要进行写操作时最好先执行擦出命令,这样写入的速度就能大大提高。进行擦除操作时不管是标准卡还是大容量卡都按块操作执行,也就是一次擦除至少 512 字节。

      7. 对标准卡进行字节操作时,起始和终止必须在一个物理扇区内,否则将不能进行读写操作。实际操作过程中建议用块操作以提高效率。不管是标准卡还是大容量卡一个读写命令只能对一个块进行操作,不允许跨物理层地址操作。

      8. 在写数据块前要先写入若干个 dummy data 字节,写完一个块数据时,主机要监测 MISO 数据线,如果从机处于忙状态这根数据线会保持低电平,这样主机就可以根据这根数据线的状态以决定是否发送下一个命令,在从机没有释放 MISO 数据线之前,主机绝对不能执行其他命令,否则将会导致写入的数据出错,而且从机也不会响应主机的命令。

      9. 在 SPI 模式下, CRC 校验是被忽略的,但依然要求主从机发送 CRC 码,只是数值可以是任意值,一般主机的 CRC 码通常设为 0x00 或 0xFF 。

      读多块操作和写多块操作的传输停止形式不一样,读多块操作时用用命令 CMD12 终止传输,而写多块操作时用 Stop Tran Token( 停止传输令牌,值为 0xFD) 终止传输。

    ----------------------------------------------------------------------------------------

    1、初始化步骤:
      (1) 延时至少 74clock,等待SD卡内部操作完成,在MMC协议中有明确说明。
      (2) CS低电平选中SD卡。
      (3) 发送 CMD0 ,需要返回 0x01 ,进入 Idle 状态
      (4) 为了区别SD卡是2.0还是1.0,或是MMC卡,这里根据协议向上兼容的原理,首先发送只有SD2.0才有的命令CMD8,如果CMD8返回无错误,则初步判断为2.0卡,进一步发送命令循环发送 CMD55+ACMD41 ,直到返回 0x00 ,确定SD2.0卡初始化成功,进入Ready 状态,再发送CMD58命令来判断是HCSD还是SCSD,到此SD2.0卡初始化成功 。如果CMD8返回错误则进一步判断为1.0卡还是MMC卡,循环发送CMD55+ACMD41 ,返回无错误,则为SD1.0卡,到此SD1.0卡初始成功,如果在一定的循环次数下,返回为错误,则进一步发送CMD1进行初始化,如果返回无错误,则确定为MMC卡,如果在一定的次数下,返回为错误,则不能识别该卡,初始结束。
      (5)CS拉高。
    2、读步骤:
      (1) 发送 CMD17 (单块)或 CMD18 (多块)读命令,返回 0x00
      (2) 接收数据开始令牌 0xfe (或 0xfc ) + 正式数据 512Bytes + CRC 校验 2Bytes, 默认正式传输的数据长度是 512Bytes ,可用 CMD16 设置块长度。
    3、 写步骤:
      (1) 发送 CMD24 (单块)或 CMD25 (多块)写命令,返回 0x00
      (2) 发送数据开始令牌 0xfe (或 0xfc ) + 正式数据 512Bytes + CRC 校验 2Bytes
    4、 擦除步骤:
      (1) 发送 CMD32 ,跟一个参数来指定首个要擦除的起始地址( SD 手册上说是块号)
      (2) 发送 CMD33, ,指定最后的地址
      (3) 发送 CMD38 ,擦除指定区间的内容
      此 3 步顺序不能颠倒。

    六、SD卡的命令格式及解析

    1.SD卡命令组成

    SD卡的指令由6字节(Byte)组成,如下:

      Byte1:0 1 x x x x x x(命令号,由指令标志定义,如CMD39为100111即16进制0x27,那么完整的CMD39第一字节为01100111,即0x27+0x40)

      Byte2-5:Command Arguments,命令参数,有些命令没有参数

      Byte6:前7位为CRC(Cyclic Redundacy Check,循环冗余校验)校验位,最后一位为停止位0

    2.SD卡的命令

    SD卡命令共分为12类,分别为class0到class11,不同的SDd卡,主控根据其功能,支持不同的命令集,如下:

      Class0 :(卡的识别、初始化等基本命令集)

        CMD0:复位SD 卡.

        CMD1:读OCR寄存器.

        CMD9:读CSD寄存器.

        CMD10:读CID寄存器.

        CMD12:停止读多块时的数据传输

        CMD13:读 Card_Status 寄存器

      Class2 (读卡命令集):

        CMD16:设置块的长度

        CMD17:读单块.

        CMD18:读多块,直至主机发送CMD12为止 .

      Class4(写卡命令集) :

        CMD24:写单块.

        CMD25:写多块.

        CMD27:写CSD寄存器 .

      Class5 (擦除卡命令集):

        CMD32:设置擦除块的起始地址.

        CMD33:设置擦除块的终止地址.

        CMD38: 擦除所选择的块.

      Class6(写保护命令集):

        CMD28:设置写保护块的地址.

        CMD29:擦除写保护块的地址.

        CMD30: Ask the card for the status of the write protection bits

      class7:卡的锁定,解锁功能命令集

      class8:申请特定命令集 。

      class10 -11 :保留

    3.有关sd卡驱动和fat fs的实现用了3个文件来实现。

    sdboot.c为sd的驱动(可理解为pdd)层,主要实现一些对sd控制器的配置以及一些基本sd命令的实现和对sd 卡的操作。

    sdmmc.c实现了从sd卡读取nk并跳到内存去运行的代码(基本可以理解为sd驱动的mdd层)。

    sdfat.c文件就是实现fat fs的。mdd层通过fatfs来对pdd层操作以实现读取文件。

    在整个过程中遇到了很多问题,现在列举如下:

      1)sd卡初始化问题

          配置gpio有关sd的功能:SDCMD, SDDAT[3:0]。

          使能CLKCON中的SDI位。

          时钟以及计算公式:SDIPRE   = PCLK/(CLK)-1;INICLK=300000;SDCLK=24000000; MMCCLK= 15000000

          cmd0-cmd55-cmd41-cmd2-cmd3-cmd7-cmd6-cmd17

      2)对sd卡操作问题

          SD卡包括:一个标识寄存器CID,一个相应地址寄存器RCA,一个其他参数寄存器CSD。

          对sd卡的操作是驱动通过sd controller来发相应的命令以达到读写等操作的:发送命令通过SDICmdCon[7:0]的除了开始2bit:CmdIndex放置要发送的命令号;SDICmdCon[8]开始发送命令来完成的。

          检测卡的插入,直接用中断引脚的电平来判断。

          判断插入的卡是否是sd卡,用命令cmd55和cmd41,因为mmc卡对cmd55不做回应。

          命令9 就是获取sd卡中csd寄存器的值的,该值包括很多sd卡的信息,其中就有sd卡的容量。这个值在sd卡接收到cmd9之后会以response的形式存放在sd控制器的SDI Response Register[0,1,2,3]中。在执行cmd9,cmd10等这样的命令的时候,卡的状态应该是不选中的,或直接在执行它们之前发送 cmd7(0)不选中卡,不然的话会timeout。

          用cmd17 来读取单个block的数据,该命令要带地址参数(该参数通过cmd3命令来获取),然后根据SDIDSTA和SDIFSTA状态值来从sd 控制器的SDIDAT寄存器中读出要读的数据。该命令与cmd9相反,在执行它之前要选中卡。读完一个block之后要做一些善后工作,为下次读取做好准备,不然的话checkcmdend就要一直循环了。因为用的是每次都读一个block,并地址要以block对齐,这样就要考虑要读取的地址是否是 block对齐的,长度是否够一个block。

          SDIDCON这个数据控制寄存器也很重要,一些对数据的操作形式就是在这里设置的。

      3)fat文件系统问题

          根据MBR找到分区表,根据分区表找到该分区MBR[446B+4个分区表(每个16B)+2B结束符)

          分区表中的第9-12字节为该分区的启始地址(单位没sector),第13-16字节为分区的长度(单位也是sector)

        http://hjx5548.blog.163.com/blog/static/563676392009111704249875/

    六、实例

    一、概述

      最近在研究WIFI驱动,驱动模块为broamd4330,基于SDIO接口,所以趁机研究了一下内核中对于SDIO设备的注册。

      (我使用的linux内核版本为3.2.0    硬件为samsung 4412)

      在介绍内核之前,有必要先了解一下MMC  SD  SDIO三种卡,从发展历程来看,是先有MMC卡,后来有SD卡,这两种都是纯粹的存储卡,而SDIO是什么呢,从字面意思理解,应该是SD+IO,也就是既有存储功能,又有IO控制功能,不过也有纯IO功能的SDIO设备(本人用到的WIFI模块就是这种)。并且,这三种卡可以使用同一个插槽,系统还能正确的识别!!,可能是由于历史原因,在开始有Linux的时候,还只存在mmc卡(不存在SD和SDIO卡),所以在linux系统里面关于这三种卡的名称统统用“mmc“来命名。

            下面来看一下CPU与WIFI模块的物理连接图

                               

          从图上可以看出,我们的WIFI模块接的是CPU上的mmc3,数据线,时钟线以及命令线都一一对应。

      当然在CPU一端,对于mmc3模块,还有一个很重要的引脚--“xmmc3CDn”脚,CPU就是根据该引脚的电平高低来判断mmc3模块上是否有卡接入,如果电平为低,表示有卡,如果为高,表示无卡,笔者这里将该引脚固定拉低。

      同时在WIFI模块一端,也有一个很重要的引脚--“WL_SDIO_SPI_HSCI_SEL”引脚   ,它是用来选择模块是工作在SD模式(低电平),还是SPI模式(高电平),笔者这里也将该引脚固定拉低。

      好了,简单的介绍了一些概念以及硬件后,还是要回归到程序上,从大的方面来讲,MMC/SD/SDIO的驱动程序主要分为两大块,主设备驱动和从设备驱动。对于上面的例子来说,CPU上的MMC3模块就是主设备,而WIFI模块就是从设备。该系列的博文就是分析MMC主设备在内核中的注册,以及对于同一个mmc插槽,系统是如何区分出MMC SD 以及SDIO设备的。

    二、host注册过程

      上面说到了MMC/SD/SDIO(以下简称MMC)的驱动从大的方面来说分为主设备驱动和从设备驱动,那本文就来详细的讲述主设备驱动注册的过程。

      MMC主设备(也就是host)指的是集成于CPU内部的MMC controller,笔者用的是4412芯片,从datasheet可以看出,里面集成了四个MMC controller,分别是mmc0,mmc1,mmc2,mmc3。 并且从上一篇文章我们知道,WIFI模块是接在mmc3 这个host上面。

      在linux系统中,将每个host设备封装成platform_device来逐一进行注册。

      对于笔者所使用的内核(3.2.0版本)来说,每一个host设备所对应的platform_device文件位于目录($KERNEL_SOURCE)/arch/arm/plat-samsung下,分别为dev-hsmmc.c,dev-hsmmc1.c,dev-hsmmc2.c,dev-hsmmc3.c,为了与实际WIFI模块对应,我们重点进入dev-hsmmc3.c文件看一看:

      从上图可以看出,该文件里面定义了一个名为s3c_device_hsmmc3的platform_device,但是定义好了的platform_device还需要有一个注册的过程,该过程就发生在文件($KERNEL_SOURCE)/arch/arm/mach-exynos/mach-$(BOARD).c中,其中有如下的一个函数调用:
                                               
          
      它的行为就是将数组skd4x12_devices里面的每一个platform_device项一一注册进系统,并且这个数组里面就包含了上面所定义的s3c_device_hsmmc3:

                                          

            

      所以总结来说,系统化在初始化的时候,就已经将s3c_device_hsmmc3(也就是那个host  mmc3)注册进了platform总线(其他的mmc0,mmc1,mmc2都是一个道理)。

      当然,对于熟悉platform机制的朋友来说,此时仅仅只是注册了platform_device ,而对应的platform_driver还没有注册。

      下面就来说说这个platform_driver的注册,它是在$(KERNEL_SOURCE)/drivers/mmc/host目录下的sdhci-s3c.c文件中进行的,该文件中有如下的一个注册函数调用:

         

      其中的参数sdhci_s3c_driver就是上面所说的platform_driver,它也是定义在sdhci-s3c.c文件中,来看一下:

          

      在对sdhci_s3c_driver进行注册的过程中,系统会根据sdhci_s3c_driver->driver.name成员变量(此处是“s3c-sdhci”)在platform_bus 总线上寻找同名字的platform_dvice(这个过程称之为“探测”),通过上面对s3c_device_hsmmc3的注册分析,发现s3c_device_mmc3.name也刚好是“s3c-sdhci”,所以他俩刚好可以配对,探测成功,同时当大家查阅s3c_device_hsmmc,s3c_device_hsmmc1以及s3c_device_hsmmc2的时候发现他们的name成员变量都是“s3c-sdhci”,,所以会有四次成功的探测,每一次探测成功,就会调用sdhci_s3c_driver.probe函数---sdhci_s3c_probe,这个函数至关重要,在整个驱动注册过程中起着核心作用。

      

      上面文章说到了探测函数sdhci_s3c_probe,现在就来仔细分析这个函数的作用:

          在分析代码之前,先简要的概括一下这个函数的功能:

      1、既然是讲host的注册,那么首先必须构造出一个host,这个host就是通过sdhci_alloc_host函数来构造出来的,它是一个struct sdhci_host类型的结构体。同时,也通过mmc_alloc_host函数构造了一个struct mmc_host的结构体变量mmc。

      2、初始化host的时钟,设置host的gpio等等其他一些“乱七八糟”的参数初始化(需要的时候再详细分析)。

      3、通过sdhci_add_host函数来注册host。

      下面重点来看sdhci_add_host函数

      该函数主要是对mmc的注册,同样mmc也有很多的参数,先来看看他的操作函数集mmc->ops = &sdhci_ops

            

      其中,request函数指针指向的函数用来处理host向从设备发送命令的请求,

                  set_ios用来设置电源、时钟等等之类(需要重点关注),

                  get_ro用来判断是否写保护

        再来看该函数里面的中断注册部分

                

      我们先看一下mmc_add_host这个函数,它的功能就是通过device_add函数将设备注册进linux设备模型,最终的结果就是在sys/bus/platform/devices目录下能见到s3c-sdhci.1,s3c-sdhci.2,s3c-sdhci.3设备节点。

      中断注册函_irq的第一个参数中断号就取自于s3c_device_hsmmc3.resource里面的irq参数,sdhci_irq就是中断服务程序,该中断函数一般在插卡、拔卡或者从设备反馈给host信息时会被调用数request

             中断服务程序

             

      程序首先读取寄存器NORINTSTSn的值,该寄存器中有两个bit分别来表示卡的插入与拔出过程(注意,必须是动态变化过程,才会让相应的两个bit置1),那么接下来的if语句就是从该寄存器的那两个bit来判断是否有卡的插入或拔出,并同时清除这两个bit,准备下一次的检测,紧接着就调用中断的下半部分函数 sdhci_tasklet_card,其实这个函数也没做什么事情,就是判读如果此时有卡的话就通过mmc_detect_chang函数调用mmc_rescan函数。从这个函数的名字都可以猜出个八九不离十,它的功能就是扫描所插入的卡

        扫描卡的程序

          

      这个函数我们重点关注上述两个地方,其实真正的扫描动作发生在函数mmc_rescan_try_freq函数里面,该函数的第二个参数表示以什么样的频率去进行扫描,那么可选的频率值在那个数组freqs里面,一般当用某个频率值扫描成功后,就直接退出了,否则,会以下一个更低的频率值来扫描,笔者所使用的WIFI模块就是以400KHz的频率扫描成功的。

           扫描过程

          

      该函数首先发送复位命令(不过该命令只有SDIO类型的卡才能够识别),然后发送CMD0,让设备进入IDLE模式,紧接着发送CMD8,获取该卡所支持的电压值,最后就是重点了(从1998-2003行),从所调用的各个函数名字可以看出,它是在试探该卡是否为SDIO? SD? MMC?

         那么接下来的文章就是要分析上面的三个函数,看它是如何识别SDIO、SD、MMC的。

    三、SDIO的识别和操作

      从上面文章的最后,我们知道host在扫描卡的过程中,其识别的顺序为SDIO  SD MMC,并且从它的注释可以看出,这个顺序是很重要的。那这篇文章,我们就看看SDIO的识别过程,它对应的函数就是mmc_attach_sdio(host) (函数位于文件drivers/mmc/core/sdio.c)

      这个函数大概来说做了如下的工作

        1、向卡发送CMD5命令,该命令有两个作用:

          第一,通过判断卡是否有反馈信息来判断是否为SDIO设备(只有SDIO设备才对CMD5命令有反馈,其他卡是没有回馈的);

          第二,如果是SDIO设备,就会给host反馈电压信息,就是说告诉host,本卡所能支持的电压是多少多少。

        2、host根据SDIO卡反馈回来的电压要求,给其提供合适的电压。

        3、初始化该SDIO卡

        4、注册SDIO的各个功能模块

        5、注册SDIO卡

      对于以上功能的具体解释,下面将结合程序娓娓道来

      1、CMD5命令的发送

                  

      第789行的函数就是发送的CMD5命令,如果卡对该命令有回馈的话,err就是0,否则,err为非0,直接退出了;并且需要重点说明的一点就是,该函数的最后一个参数ocr,它是存储反馈命令的,SDIO设备对CMD5的反馈命令为R4,下面来仔细分析一下这个R4,因为后面要用到这个R4命令。从SDIO spec文档里面,我们能得到R4命令的格式

                

      从上图可以看出,该命令有48位,但我们的ocr变量是32位的,那怎么存储呢?系统就去掉原命令的开头8位以及结尾的8位,只保留中间的32为,也就是截短后的命令格式是如下:

                

            

      具体各位的描述如下:

        C --   我还不知道

        Number 0f IO functions   -- 每个SDIO设备都有功能块,这三位就记录了该设备有多少个功能块,最多7个

        Memory Present – 指明该设备是纯粹只有功能块的设备,还是同时包含了存储空间,如果为0就是前者,如果是1就是后者

        Stuff Bits  -- 没有实际用途一般为0

        I/O OCR – 该设备所能支持的电压范围(具体描述见sdio spec)

      2、配置电压

             

      ocr就是我们上面讲的反馈命令R4(截短之后的32位),那么ocr&0x7f的意义是什么呢?从R4的格式就可以看出来,其低24位就代表了所能支持的电压范围,我们再来详细的看一下这24位的OCR格式

                    

      现在应该可以知道ocr&0x7f的意义了吧,就是摈弃那些保留的电压范围。

      重点关注mmc_select_voltage

                      

      第1080行的相与 过程就是判断host实际所支持的电压与card所需要的电压是否匹配,如果匹配,那么ocr的值就非0,否则就为0

      简单介绍下第1082行的ffs函数,它的作用就是返回参数中第一个为1的bit的位置(ffs(0)=0,ffs(1)=1,ffs(8)=4),那么该函数用在这里的作用就是取出card需要的实际电压是多少;

      第1090行的mmc_set_ios函数里面通过调用sdhci_set_power将host->ios.vdd所代表的电压写入寄存器PWRCONn中 完成那个对电压的重新配置(想要了解更详细的过程,请跟踪源代码)

      3、初始化SDIO卡

                   

      第821行就是初始化SDIO卡的函数  这个函数很长,也很重要,这里笔者就不列出其程序代码了,只是列出其中最重要的几条:

        1、通过函数mmc_alloc_card分配一个mmc_card的变量card

        2、通过读取R4命令中的bit27(也就是Memory Present)来判断此卡是纯IO卡 ,还是同时包含存储功能。笔者使用的WIFI模块为纯IO功能,所以card->type = MMC_TYPE_SDIO(这个很重要,以后会用到) (接下来重点分析MMC_TYPE_SDIO的情况)

        3、通过发送CMD3命令获取设备的从地址(relative addr),并且存放在变量card->rca中。笔者使用的WIFI模块的card->rca = 1

        4、通过发送CMD7,选中相应从地址的卡

        5、通过调用函数mmc_set_clock设置卡工作的时钟频率

        6、通过发送CMD52命令,设置4位数据传输模式

      4、注册SDIO功能模块

                 

      847行的变量funcs存储该SDIO卡所包含的IO功能块的个数,851行到857行就是逐一初始化各个IO功能块,下面来重点看一下该函数的内容:

                  

      第71行就是分配sdio_func结构体变量,该结构体存储了功能块的参数。

      第75行就是给功能块编号,编号是从1到7(因为一个SDIO设备最多只有7个功能块),存储在变量func->num中

      第78行就是读取SDIO卡中的FBR寄存器中关于该卡的功能类型的数据,存储在func->class变量中(具体关于FBR寄存器内容,可以参考SDIO spec文档)

      第82行就是读取SDIO卡中的CIS寄存器的内容

               

      上面的程序就是将功能模块逐个的注册进设备模型,这里想重点说明一下注册的名称(name),它是由三部分组成的,每部分之间用冒号隔开,(即 host的名称:rca:功能块编号)。

      具体到笔者使用的WIFI模块,因为其host名称是mmc2  ,rca = 1,并且有两个功能模块(功能模块编号分别是1和2),所以在/sys/bus/sdio/devices目录下能见到如下两个设备名

        mmc2:0001:1

        mmc2:0001:2

      5、注册SDIO卡

           

      上面的mmc_add_card函数就是注册card了(这个card是在第3部分,初始化SDIO卡 里面分配和定义的)

         

            

      第259行就是给card命名,格式为host名字:从地址,对于笔者的WIFI模块 就是mmc2:0001

      第261到273行就是根据card->type来分辨出card的类型,给赋予相应的字符串,笔者的WIFI模块就是"SDIO"

      第275行就是打印信息,具体不解释 笔者的打印信息为  mmc2:new high speed SDIO card at address 0001(通常可以通过查看内核启动信息中是否有该语句来判断card是否被正确识别)

      第283行 就是将card注册进linux设备模型  注册结果就是可以在/sys/bus/mmc/devices目录下见到card 的名字,笔者的就是mmc2:0001

  • 相关阅读:
    DataAnnotations
    使用BizTalk实现RosettaNet B2B So Easy
    biztalk rosettanet 自定义 pip code
    Debatching(Splitting) XML Message in Orchestration using DefaultPipeline
    Modifying namespace in XML document programmatically
    IIS各个版本中你需要知道的那些事儿
    关于IHttpModule的相关知识总结
    开发设计的一些思想总结
    《ASP.NET SignalR系列》第五课 在MVC中使用SignalR
    《ASP.NET SignalR系列》第四课 SignalR自托管(不用IIS)
  • 原文地址:https://www.cnblogs.com/lihuidashen/p/6112169.html
Copyright © 2011-2022 走看看