视频1 视频21 视频41 视频61 视频文章1 视频文章21 视频文章41 视频文章61 推荐1 推荐3 推荐5 推荐7 推荐9 推荐11 推荐13 推荐15 推荐17 推荐19 推荐21 推荐23 推荐25 推荐27 推荐29 推荐31 推荐33 推荐35 推荐37 推荐39 推荐41 推荐43 推荐45 推荐47 推荐49 关键词1 关键词101 关键词201 关键词301 关键词401 关键词501 关键词601 关键词701 关键词801 关键词901 关键词1001 关键词1101 关键词1201 关键词1301 关键词1401 关键词1501 关键词1601 关键词1701 关键词1801 关键词1901 视频扩展1 视频扩展6 视频扩展11 视频扩展16 文章1 文章201 文章401 文章601 文章801 文章1001 资讯1 资讯501 资讯1001 资讯1501 标签1 标签501 标签1001 关键词1 关键词501 关键词1001 关键词1501 专题2001
sdio linux驱动流程图
2025-10-02 19:17:27 责编:小OO
文档
SD卡的工作原理

想了解SD卡的工作原理,首先需要了解的就是SD卡协议了,这个在网上可以轻松的下载到。在了解协议后,就可以看看下面的一些开发思路了。首先看下脱离操作系统如何在S3C2410上实现SD卡的读写。过程可以分为3个大的步骤:初始化sd卡、写sd卡、读sd卡;下面的过程是我通过realview-MDK环境测试过的。

一、初始化sd卡 

二、写sd卡  写sd卡可以分为3种方式:POLL、中断、DMA    

(1)POLL写

(2)中断写

(3)DMA写

三、读sd卡   读sd卡也可分为3中方式:POLL、中断、DMA    

(1)POLL读

(2)中断读

(3)DMA读

SD卡linux驱动工作原理,说了下脱离操作系统如何在S3C2410上实现SD卡的读写。了解了脱离操作系统的工作原理后,现在可以思考linux是如何管理管理SD卡的了。Linux中SD驱动可以分为3层:块设备层(mmc_block.c ,mmc_sysfs.c,mmc_queue.c)、mmc协议层(mmc.c)、sd驱动层(s3c2410_sdi.c)。

下面从以下几个方面理解驱动:

1、s3c2410_sdi.c代码初始化过程;

2、SD卡块设备注册过程; 

3、request及数据传输的实现。

下面介绍的过程参考的代码是内核版本是2.6.8,其它版本过程类似。

一、s3c2410_sdi.c代码初始化过程 

二、SD卡块设备注册过程

三、request及数据传输的实现

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)终止传输。

为了区别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卡,如果在一定的次数下,返回为错误,则不能识别该卡,初始结束。

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卡就是一个存储器,只不过用命令的方式来进行操作,我们只要掌握了各条命令及操作方式,就可以灵活的操作SD卡了,另外我所了解的IC卡也是类似的原理,还有就是建议开始看MMC的协议,简单明了易懂些,有了对MMC卡的一些了解后看SD卡协议就容易多了。

1.SD/MMC/SDIO 概念区分概要 

SD (Secure Digital )与 MMC (Multimedia Card ) 

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

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 来读取。 

SD 的 MMC Mode 

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 的写法来做清楚区别。

2.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(struct s3cmci_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(struct s3cmci_host), &pdev->dev);

mmc->ops = &s3cmci_ops; …………… 

s3cmci_ops 中包含了四个函数:

static struct mmc_host_ops s3cmci_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(struct mmc_host *mmc, struct mmc_ios *ios) 

依据核心层传递过来的 ios ,来设置硬件 IO, 包括引脚配置,使能时钟,和配置总线带宽。 

s3cmci_request : 这个 函数是最主要,也最复杂的函数,实现了命令和数据的发送和接收, 

当 CORE 部分需要发送命令或者传输数据时,都会调用这个函数,并传递 mrq 请求。 

static void s3cmci_request(struct mmc_host *mmc, struct mmc_request *mrq) 

      struct s3cmci_host *host = mmc_priv(mmc); 

host->status = "mmc request";

host->cmd_is_stop = 0;

host->mrq = mrq;

       if (s3cmci_card_present(mmc) == 0) { 

              dbg(host, dbg_err, "%s: no medium present\\n", __func__); 

host->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)

/ because host->dodma = 0,so we don't use it

res = s3cmci_prepare_dma(host, cmd->data);// 准备 DMA 传输, 

              else 

res = s3cmci_prepare_pio(host, cmd->data);.// 准备 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; 

/*with start 2bits*/ 

if (cmd->flags & MMC_RSP_PRESENT)

                   ccon |= S3C2410_SDICMDCON_WAITRSP; 

/*wait rsp*/ 

if (cmd->flags & 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); 

              /* Determines SDI generate an interrupt if Tx FIFO fills half*/ 

              enable_imask(host, S3C2410_SDIIMSK_TXFIFOHALF); 

       } else { 

              enable_imask(host, S3C2410_SDIIMSK_RXFIFOHALF 

                          | S3C2410_SDIIMSK_RXFIFOLAST); 

       } 

如果是写数据到 SD 的话,会调用 do_pio_write, 往 FIFO 中填充数据。当  字节的 FIFO 少于 33 字节时就会产生中断。如果是从 SD 读数据,则先使能中断,当 FIFO 多于 31 字节时时,则会调用中断服务程序,中断服务程序中将会调用 do_pio_read FIFO 的数据读出。 

接下来分析 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, &host->pio_bytes,

                   /* If we have reached the end of the block, we have to 

                     * write exactly the remaining number of bytes.  If we 

                     * in the middle of the block, we have to write full 

                     * words, so round down to an even multiple of 4. */ 

if (fifo >= host->pio_bytes)//fifo 的空间比 pio_bytes 大,表明这是读这个块的最后一次 

fifo = host->pio_bytes;

                   /* because the volume of FIFO can contain the remaning block*/ 

                   else 

                            fifo -= fifo & 3;/*round down to an even multiple of 4*/ 

host->pio_bytes -= fifo;// 更新还剩余的没有写完的字 

host->pio_count += fifo;/*chang the value of pio_bytes*/

fifo = (fifo + 3) >> 2;// 将字节数转化为字数 

                   /*how many words fifo contain,every time we just writ one word*/ 

ptr = host->pio_ptr;

                   while (fifo--) 

                            writel(*ptr++, to_ptr);// 写往 FIFO. 

host->pio_ptr = ptr;

         } 

注释一:注意, MMC 核心为 mrq->data 成员分配了一个 struct scatterlist 的表,用来支持分散聚集,使用这种方法,这样使物理上不一致的内存页,被组装成一个连续的数组,避免了分配大的缓冲区的问题 

我们看代码 

if (host->pio_sgptr >= host->mrq->data->sg_len) {

                   dbg(host, dbg_debug, "no more buffers (%i/%i)\\n", 

host->pio_sgptr, host->mrq->data->sg_len);

                   return -EBUSY; 

         } 

sg = &host->mrq->data->sg[host->pio_sgptr];

*bytes = sg->length;// 页缓冲区中的长度 

         * pointer = sg_virt(sg); 将页地址映射为虚拟地址 

host->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 引起的吗 

接下来我们分析一下中断服务程序: 

static irqreturn_t s3cmci_irq(int irq, 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->base + host->sdiimsk);

这些将作为中断处理的依据。 

如果不是 DMA 模式,则处理数据的收发 

if (!host->dodma) {

if ((host->pio_active == XFER_WRITE) &&

                       (mci_fsta & S3C2410_SDIFSTA_TFDET)) { 

/*This bit indicates that FIFO data is available for transmit when 

DatMode is data transmit mode. If DMA mode is enable, sd 

host requests DMA operation.*/ 

                            disable_imask(host, S3C2410_SDIIMSK_TXFIFOHALF); 

tasklet_schedule(&host->pio_tasklet);/*注意我们采用 tasklet 这种延时机制来减少中断服务的时间,延时函数 pio_tasklet 中调用了 do_pio_write 和 了 do_pio_read */

host->status = "pio tx";

                   } 

if ((host->pio_active == XFER_READ) &&

                       (mci_fsta & S3C2410_SDIFSTA_RFDET)) { 

                            disable_imask(host,  S3C2410_SDIIMSK_RXFIFOHALF | 

                                           S3C2410_SDIIMSK_RXFIFOLAST); 

tasklet_schedule(&host->pio_tasklet);

host->status = "pio rx";

                   } 

接下来的很多代码是对其他的一些类型中断的处理。 

最后来分析 DMA 模式:这种模式下不需要 CPU 的干预。 S3C2440 的 DMA 有 4 个通道,我们选择了通道 0 

static int s3cmci_prepare_dma(struct s3cmci_host *host, struct mmc_data *data) 

         int dma_len, i; 

int rw = (data->flags & MMC_DATA_WRITE) ? 1 : 0;

BUG_ON((data->flags & BOTH_DIR) == BOTH_DIR);

         s3cmci_dma_setup(host, rw ? S3C2410_DMASRC_MEM : S3C2410_DMASRC_HW); // 注一 

         s3c2410_dma_ctrl(host->dma, S3C2410_DMAOP_FLUSH); 

dma_len = dma_map_sg(mmc_dev(host->mmc), data->sg, data->sg_len,

                                 (rw) ? DMA_TO_DEVICE : DMA_FROM_DEVICE); // 注二 

         if (dma_len == 0) 

                   return -ENOMEM; 

host->dma_complete = 0;

host->dmatogo = dma_len;

for (i = 0; i < dma_len; i++) {

                   int res; 

                   dbg(host, dbg_dma, "enqueue %i:%u@%u\\n", i, 

sg_dma_address(&data->sg[i]),

sg_dma_len(&data->sg[i]));

                   res = s3c2410_dma_enqueue(host->dma, (void *) host,

sg_dma_address(&data->sg[i]),

sg_dma_len(&data->sg[i]));

                   if (res) { 

                            s3c2410_dma_ctrl(host->dma, S3C2410_DMAOP_FLUSH); 

                            return -EBUSY; 

                   } 

         } 

         s3c2410_dma_ctrl(host->dma, S3C2410_DMAOP_START); 

         return 0; 

注一 : 这个函数先调用 s3c2410_dma_devconfig 来配置 DMA 源 / 目的的意见类型和地址,注意我们这里的设备地址 host->mem->start + host->sdidata 实际上就是 SDIDATA 寄存器的地址值,如果是写 SD 卡,则为目的地址,否则为源地址。然后调用 s3c2410_dma_set_buffdone_fn(host->dma, s3cmci_dma_done_callback);设置 dma 通道 0 的回调函数。 

注二: 

dma_len = dma_map_sg(mmc_dev(host->mmc), data->sg, data->sg_len,

                                 (rw) ? DMA_TO_DEVICE : DMA_FROM_DEVICE); 

这里进行分散 / 聚集映射( P444,LDD3 ) , 返回值是传送的 DMA 缓冲区数,可能会小于 sg_len ,也就是说 sg_len 与 dma_len 可能是不同的。 

sg_dma_address(&data->sg[i]), 返回的是总线( DMA )地址 

sg_dma_len(&data->sg[i])); 返回的是缓冲区的长度。 

最后调用 s3c2410_dma_enqueue(host->dma, (void *) host,

sg_dma_address(&data->sg[i]),

sg_dma_len(&data->sg[i]));

对每个 DMA 缓冲区进行排队,等待处理。 

s3c2410_dma_ctrl(host->dma, S3C2410_DMAOP_START); 启动 DMA 

这样 DMA 缓冲区就和 scatterlist 联系起来,当写数据时, scatterlist 中的数据由于上面的映射关系会直接“拷贝”到 DMA 缓冲区,当读数据时则反之。整个过程不需要 CPU 干预,自动完成。 

以上就是针对 mini2440 HOST 部分的内容。 

4 、 CORE 层分析: 

CORE 层完成了不同协议和规范的实现,并为 HOST 层的驱动提供了接口函数,在 HOST 层我们曾经调用的两个函数: 

mmc_alloc_host(sizeof(struct s3cmci_host), &pdev->dev);

mmc_add_host(mmc); 

我们就从这两个函数入手,来分析 CORE 层与 HOST 层是如何交互的。 

先看 mmc_alloc_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;

device_initialize(&host->class_dev);

这几句是将导致在 /SYS/CLASS/mmc_host 下出现 mmc0 目录,添加类设备,在 2.6.21 后的版本中,类设备的 class_device 已近被 device 所取代, LDD3P387 的内容有点 OUT 了 

INIT_DELAYED_WORK(&host->detect, mmc_rescan);

初始化了一个工作队列,延时函数为 mmc_rescan ,这个延时函数很重要,下午要详细分析 

最后对 host 做一些默认配置,不过这些配置在 probe 函数的后面都被重置了。 

分析 mmc_add_host(mmc); 

device_add(&host->class_dev); 这里才真正的添加了类设备。 

其中调用了 mmc_start_host 

void mmc_start_host(struct mmc_host *host) 

       mmc_power_off(host); 

       mmc_detect_change(host, 0); 

mmc_power_off 中对 ios 进行了设置,然后调用 mmc_set_ios(host); 

host->ios.power_mode = MMC_POWER_OFF;

host->ios.bus_width = MMC_BUS_WIDTH_1;

host->ios.timing = MMC_TIMING_LEGACY;

       mmc_set_ios(host); 

mmc_set_ios(host) 中的关键语句 host->ops->set_ios(host, ios); 这里的 set_ios 实际上就是我们前面所提到的 .set_ios  = s3cmci_set_ios, 

再看 mmc_detect_change(host, 0); 最后一句是 

mmc_schedule_delayed_work(&host->detect, delay);

实际上就是调用我们前面说的延时函数 mmc_rescan 

mmc_power_up(host);// 这个函数实际上与前面的 mmc_power_off 类似,不过设置了启动时需要的 ios 

           mmc_go_idle(host); 

           //CMD0 , from inactive to idle 

           mmc_send_if_cond(host, host->ocr_avail);// 发送 SD_SEND_IF_COND ,是使用 SD2.0 卡才需要设置的命令 

/*suppot for 2.0 card*/ 

                     * ...then normal SD... 

                     */ 

                   err = mmc_send_app_op_cond(host, 0, &ocr); 

                   if (!err) { 

                            if (mmc_attach_sd(host, ocr)) 

                                     mmc_power_off(host); 

                            goto out; 

                   } 

蓝色部分是遵照 SD 卡协议的 SD 卡启动过程,包括了非激活模式、卡识别模式和数据传输模式三种模式共九种状态的转换,你需要参照相关规范来理解。可以先参考下面三章图对模式和状态,以及状态转换有个初步了解。 

我们最初的 SD 卡的状态时 inactive 状态调用 mmc_go_idle(host) 后,发送命令 CMD0 是其处于 IDLE 状态。 

我们详细分析一下 mmc_go_idle 

memset(&cmd, 0, sizeof(struct mmc_command)); 

         cmd.opcode = MMC_GO_IDLE_STATE; MMC_GO_IDLE_STATE 就是命令 CMD0 

         cmd.arg = 0; 此命令无参数 

         cmd.flags = MMC_RSP_SPI_R1 | MMC_RSP_NONE | MMC_CMD_BC; 

         err = mmc_wait_for_cmd(host, &cmd, 0);// 见注 1 

         mmc_delay(1); 

  

注 1 : mmc_wait_for_cmd(host, &cmd, 0) 是用来发送命令的,我们揭开它的神秘面纱吧。 

memset(&mrq, 0, sizeof(struct mmc_request)); 

memset(cmd->resp, 0, sizeof(cmd->resp));

cmd->retries = retries;

         mrq.cmd = cmd; 将命令嵌入到一个 mmc 请求中 

cmd->data = NULL;mmc 命令的 data 部分设置为 NULL, 这样表示我们要传输的是命令而不是数据 

         mmc_wait_for_req(host, &mrq);// 关键部分 

在该函数中调用了mmc_start_request ,而这个函数调用了host->ops->request(host, mrq) ,这个request 函数就是我们在前面分析的s3cmci_request ,这样MMC 核心第二次核HOST 层握手了 

我们再看看:        err = mmc_send_app_op_cond(host, 0, &ocr);// 注一 

                   if (!err) { 

                            if (mmc_attach_sd(host, ocr))// 注二 

                                     mmc_power_off(host); 

                            goto out; 

注一:实际上是要发送 ACMD41 命令,这条命令可以用来获取 SDcard 的允许电压范围值,由于这是一条应用命令,所有发送它之前需要发送 CMD_55 命令。执行完后 card 状态变为 READY 获取的电压范围保存在 ocr 中,再调用 mmc_attach_sd(host, ocr) 看这个电压范围是否满足主机的要求,不满足,则 power_off 主机。 

注二: mmc_attach_sd 完成匹配,和初始化卡的功能 

host->ocr = mmc_select_voltage(host, ocr); 看是否匹配,如果匹配则做下面初始化工作 

mmc_sd_init_card(host, host->ocr, NULL); 我们分析该函数 

(1) mmc_all_send_cid ()这个函数发生 CMD2 ,获取卡的身份信息,进入到身份状态 

(2)card = mmc_alloc_card(host, &sd_type); 分配一张 SD 类型的 card 结构 

(3) 接着调用 mmc_send_relative_add, 获取卡的相对地址,注意一前卡和主机通信都采用默认地址,现在有了自己的地址了,进入到 stand_by 状态 

( 4 )通过发送 SEND_CSD (CMD9) 获取 CSD 寄存器的信息,包括 block 长度,卡容量等信息 

(5) mmc_select_card(card) 发送 CMD7, 选中目前 RADD 地址上的卡,任何时候总线上只有一张卡被选中,进入了传输状态 , 

( 6 )调用 mmc_app_send_scr 发送命令 ACMD51 获取 SRC 寄存器的内容,进入到 SENDING-DATA 状态 

在函数中还将获得的各个卡寄存器的内容解码,并保存到 cmd 结构的相应成员中。 

( 7 ) if (host->ops->get_ro(host) > 0 )

                                     mmc_card_set_readonly(card); 

通过调用 get_ro(host) 函数,实际上就是 s3cmci_get_ro 函数了。 我们判断是否写保护,如果是的,将 card 状态设置为只读状态 

最后再 mmc_attach_sd 里,我们将 card 结构添加进去 

mmc_add_card(host->card);

dev_set_name(&card->dev, "%s:%04x", mmc_hostname(card->host), card->rca); 这里我们以 host 名 +rca 地址来命名卡我们可以看到在 /sys/devices/platform/s3c2440-sdi/mmc_host:mmc0/ 下出现 mmc0 : 0002 的目录,这个 0002 就是 rca 地址 

到这里我们分析完了 MMC 的核心层。

linux-2.6.2x的mmc驱动与linux-2.6.1x的mmc驱动的区别 

在linux-2.6.2x中,mmc驱动用到的block_device_operations结构已重新定义,请看:

linux-2.6.1x:

struct block_device_operations {

    int (*open) (struct inode *, struct file *);

    int (*release) (struct inode *, struct file *);

    int (*ioctl) (struct inode *, struct file *, unsigned, unsigned long);

    int (*media_changed) (struct gendisk *);

    int (*revalidate_disk) (struct gendisk *);

    struct module *owner;

};

linux-2.6.2x

struct block_device_operations {

    int (*open) (struct inode *, struct file *);

    int (*release) (struct inode *, struct file *);

    int (*ioctl) (struct inode *, struct file *, unsigned, unsigned long);

    long (*unlocked_ioctl) (struct file *, unsigned, unsigned long);

    long (*compat_ioctl) (struct file *, unsigned, unsigned long);

    int (*direct_access) (struct block_device *, sector_t, unsigned long *);

    int (*media_changed) (struct gendisk *);

    int (*revalidate_disk) (struct gendisk *);

    int (*getgeo)(struct block_device *, struct hd_geometry *);

    struct module *owner;

};

注意到新版本的block驱动接口结构增加了gntgeo成员,使调用者可以直接调用此函数获得设备的几何结构。 

工作流程: 

mmc驱动主要文件包括

drivers/mmc/card/block.c

drivers/mmc/card/queue.c

drivers/mmc/core/core.c

drivers/mmc/core/host.c

drivers/mmc/core/

内核启动时,首先执行core/core.c的mmc_init,注册mmc、sd总线,以及一个host class设备。接着执行card/block.c中,申请一个块设备。

数据结构: 

mmc总线操作相关函数,由于mmc卡支持多种总数据线,如SPI、SDIO、8LineMMC,而不同的总线的操作控制方式不尽相同,所以通过此结构与相应的总线回调函数相关联。

//总线操作结构

struct mmc_bus_ops {

    void (*remove)(struct mmc_host *);

    void (*detect)(struct mmc_host *);

    int (*sysfs_add)(struct mmc_host *, struct mmc_card *card);

    void (*sysfs_remove)(struct mmc_host *, struct mmc_card *card);

    void (*suspend)(struct mmc_host *);

    void (*resume)(struct mmc_host *);

};

//  mmc卡的总线操作 core/mmc.c

static const struct mmc_bus_ops mmc_ops = {

    .remove = mmc_remove,

    .detect = mmc_detect,

    .sysfs_add = mmc_sysfs_add,

    .sysfs_remove = mmc_sysfs_remove,

    .suspend = mmc_suspend,

    .resume = mmc_resume,

};

// sd卡的总线操作 core/sd.c

static const struct mmc_bus_ops mmc_sd_ops = {

    .remove = mmc_sd_remove,

    .detect = mmc_sd_detect,

    .sysfs_add = mmc_sd_sysfs_add,

    .sysfs_remove = mmc_sd_sysfs_remove,

    .suspend = mmc_sd_suspend,

    .resume = mmc_sd_resume,

};

// sdio的总线操作 core/sdio.c

static const struct mmc_bus_ops mmc_sdio_ops = {

    .remove = mmc_sdio_remove,

    .detect = mmc_sdio_detect,

};

关于总线操作的函数: 

.detect,驱动程序经常需要调用此函数去检测mmc卡的状态,具体实现是发送CMD13命令,并读回响应,如果响应错误,则依次调用.remove、detach_bus来移除卡及释放总线。

总体架构: 

kernel启动时,先后执行mmc_init()及mmc_blk_init(),以对mmc设备及mmc块模块进行初始化。

然后在挂载mmc设备驱动时,执行驱动程序中的xx_mmc_probe(),检测host设备中挂载的sd设备。此时probe函数会创建一个host设备,然后开启一个延时任务mmc_rescan()。

驱动挂载成功后,mmc_rescan()函数被执行,然后对卡进行初始化(步骤后面详细讲述)。

假如扫描到总线上挂有有效的设备,就调用相对应的函数把设备装到系统中,mmc_attach_sdio()、mmc_attach_sd()、mmc_attach_mmc()这三个函数分别是装载sdio设备,sd卡和mmc卡的。

在 sd卡中,驱动循环发送ACMD41、CMD55给卡,读取OCR寄存器,成功后,依次发送CMD2(读CID)、CMD3(得到RCA)、CMD9(读 CSD)、CMD7(选择卡)。后面还有几个命令分别是ACMD41&CMD51,使用CMD6切换一些功能,如切换到高速模式。

经过上述步骤,已经确定当前插入的卡是一张有效、可识别的存储卡。然后调用mmc_add_card()把存储卡加到系统中。正式与系统驱动连接在一起。

卡设备加到系统中后,通知mmc块设备驱动。块设备驱动此时调用probe函数,即mmc_blk_probe()函数,mmc_blk_probe()首先分配一个新的mmc_blk_data结构变量,然后调用mmc_init_queue,初始化blk队列。然后建立一个线程 mmc_queue_thread()。

mmc_rescan:mmc_rescan()函数是在驱动装载的时候,由驱动xx_mmc_probe()调用 mmc_alloc_host()时启动的一个延时任务。 xx_mmc_probe()->mmc_alloc_host()->INIT_DELAYED_WORK(&host->detect, mmc_rescan);

core部分 

1、取得总线

2、检查总线操作结构指针bus_ops,如果为空,则重新利用各总线对端口进行扫描,检测顺序依次为:SDIO、Normal SD、MMC。当检测到相应的卡类型后,就使用mmc_attach_bus()把相对应的总线操作与host连接起来。

void mmc_attach_bus(struct mmc_host *host, const struct mmc_bus_ops *ops)

{

    ...

host->bus_ops = ops;

    ...

}

3、初始化卡接以下流程初始化:

a、发送CMD0使卡进入IDLE状态

b、发送CMD8,检查卡是否SD2.0。SD1.1是不支持CMD8的,因此在SD2.0 Spec中提出了先发送CMD8,如响应为无效命令,则卡为SD1.1,否则就是SD2.0(请参考SD2.0 Spec)。

c、发送CMD5读取OCR寄存器。

d、发送ACMD55、CMD41,使卡进入工作状态。MMC卡并不支持ACMD55、CMD41,如果这步通过了,则证明这张卡是SD卡。

e、如果d步骤错误,则发送CMD1判断卡是否为MMC。SD卡不支持CMD1,而MMC卡支持,这就是SD和MMC类型的判断依据。

f、如果ACMD41和CMD1都不能通过,那这张卡恐怕就是无效卡了,初始化失败下载本文

显示全文
专题