linux驱动基础系列--Linux mmc sd sdio驱动分析

前言

  主要是想对Linux mmc子系统(包含mmc sd sdio)驱动框架有一个整体的把控,因此会忽略某些细节,同时里面涉及到的一些驱动基础,比如平台驱动、块设备驱动、设备模型等也不进行详细说明原理,涉及到sd/mmc/sdio协议部分也只会简单带过,因为linux内核mmc子系统里面已经实现了这些协议,我们以后并不需要重新实现这些,只需要对协议有个简单的了解,基于内核版本:2.6.35.6。如果有任何错误地方,请指出,谢谢!

mmc、sd、sdio介绍

  SD/MMC卡是一种大容量、性价比高、体积小、访问接口简单的存储卡。SD/MMC 卡大量应用于数码相机、MP3 机、手机、大容量存储设备,作为这些便携式设备的存储载体,它还具有低功耗、非易失性、保存数据无需消耗能量等特点。

  总的来说,sd/mmc/sdio采用的协议绝大部分是通用的,而且硬件电气特性也相似,接口可以相兼容,mmc是比较老的存储卡了,sd是mmc的替代者,sdio是基于sd而额外开发出的一种io接口卡 就好比pcmcia接口,以前主要用来存储卡上,现在wifi、蓝牙等很多设备也用pcmcia这一接口。下文涉及到具体驱动时以sd卡为主。 Sd卡有很多标准,不同的标准主要是对容量及速度进行的改进,当然支持的命令也会有变化。这些标准包括sdc、sdhc、sdxc、uhs等,sdc最大支持2g,超过2g,小于32g属于sdhc范畴。因为以sd卡为主要对象,下面对sd卡简单介绍。

更多的信息参考: http://zh.wikipedia.org/wiki/SD卡
http://zh.wikipedia.org/wiki/SDHC

SD存储卡结构图

SD存储卡结构图

  SD卡支持两种模式,分别为spi模式和sd模式(sd模式又有1bit和4bit之分)

引脚说明如下图所示:

SD引脚说明

Linux下Mmc/sd/sdio驱动分析

Mmc/sd/sdio子系统代码都在linux-2.6.35\drivers\mmc下,如下图
Mmc/sd/sdio目录结构

三个目录的作用分别为:

Card 这个目录是衔接最上层应用的接口,应用层使用sd卡一般都是通过文件系统来操作的,card目录里面的代码就是让sd卡成为一个块设备,这样应用层就可以把它当作磁盘来操作了。

Core 这个目录是MMC子系统的核心,里面实现了card和host要用到的一些通用的函数接口和数据结构,它起到衔接作用,可以看作成中间层。Mmc/sd/sdio协议部分就是在这个文件夹里面实现的(Card目录里面也会涉及到协议)。

Host 这个目录存放了各个mmc/sd/sdio控制器的代码,最终操作mmc/sd/sdio卡的部分就是在这里实现的。

Mmc/sd/sdio框架(下面简称mmc子系统)我打算分三个部分来分析

第一部分、核心部分

  核心部分肯定是在core目录里。文件core.c里mmc_init负责初始化mmc子系统subsys_initcall(mmc_init);
主要工作是:

  1. mmc_register_bus 注册mmc总线,这个总线主要是为card目录里实现的mmc设备驱动层和mmc控制器实例化一个mmc(包括sd/sdio)设备对象建立的。
  2. sdio_register_bus 这是sdio的部分,它比较特殊,需要额外的一条总线,这里不进行过多说明,以sd卡为主线

第二部分、mmc控制器驱动部分

  这部分肯定是在host目录里。以三星的文件s3cmci.c为例

static int __init s3cmci_init(void)
{
       return platform_driver_register(&s3cmci_driver);
}

类似于i2c、spi这些控制器,都是以平台设备的方式实现控制器的驱动。平台驱动就不多说了,直接看s3cmci_probe

它主要完成的工作是:

  1. mmc = mmc_alloc_host(sizeof(struct s3cmci_host), &pdev->dev);这个基本上是所有相类似驱动的必备过程,实例化一个控制器对象,不过一般都会额外需要自己定义一些数据来维护整个驱动,可以说是上下文数据,这个函数的第一个参数sizeof(struct s3cmci_host)就是分配私有的数据。mmc_alloc_host这个接口就是core目录里面的host.c提供的。在控制器驱动这边看来,mmc_alloc_host就是核心层为它隐藏了很多细节。所以分析控制器驱动的时候我们暂时不关心那些细节。
  2. 初始化自己私有部分数据,比如配置gpio、提取平台设备的信息注册中断、建立寄存器资源映射、分配dma(如果有使能)、请求检测管脚的gpio及注册中断(如果有使能detect)、请求写保护管脚gpio(如果有使能写保护)、开启控制器时钟,这部分是和具体平台的控制器有关的,不同平台控制器需要的资源可能有多又少。
  3. 初始化核心层定义的控制器对象
       mmc->ops   = &s3cmci_ops;
       mmc->ocr_avail  = MMC_VDD_32_33 | MMC_VDD_33_34;
#ifdef CONFIG_MMC_S3C_HW_SDIO_IRQ
       mmc->caps  = MMC_CAP_4_BIT_DATA | MMC_CAP_SDIO_IRQ;
#else
       mmc->caps  = MMC_CAP_4_BIT_DATA;
#endif
       mmc->f_min      = host->clk_rate / (host->clk_div * 256);
       mmc->f_max     = host->clk_rate / host->clk_div;
 
       if (host->pdata->ocr_avail)
              mmc->ocr_avail = host->pdata->ocr_avail;
 
       mmc->max_blk_count      = 4095;
       mmc->max_blk_size  = 4095;
       mmc->max_req_size = 4095 * 512;
       mmc->max_seg_size = mmc->max_req_size;
 
       mmc->max_phys_segs     = 128;
       mmc->max_hw_segs = 128;

这些是核心层会用到的,所以需要初始化它们,其中s3cmci_ops是控制器操作集,编写控制器驱动的一个主要任务就是实现这个操作集,

static struct mmc_host_ops s3cmci_ops = {
       .request = s3cmci_request,   //最终执行硬件操作的函数
       .set_ios  = s3cmci_set_ios,    //配置控制器的函数
       .get_ro         = s3cmci_get_ro,  //判断是否写保护,其实就是读写保护的gpio
       .get_cd         = s3cmci_card_present, //判断是否卡存在,其实就是读卡侦测的gpio
 
       .enable_sdio_irq = s3cmci_enable_sdio_irq, //和sdio相关,暂时忽略
};

下面简单分析下每个函数,毕竟我们写mmc驱动绝大部分工作就是写mmc控制器的驱动,S3cmci_ops提供的操作集只实现了5个函数

第一个函数s3cmci_request
  这个是最重要的函数吧,它是最终执行硬件操作流程的函数。但是它处理的是发送什么呢?是命令还是数据 这个就得看s3cmci_request的参数来,至于参数的格式就是由核心层提供,核心层更上一层的card设备驱动层调用核心层的函数,将命令或者数据发往控制器,当然,在控制器初始化过程中也会调用这些函数。下面看s3cmci_request

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

这两个参数都是核心层定义的数据类型,第一个是在probe的时候我们通过mmc_alloc_host分配的,里面还有我们自己的私有数据!第二个是mmc_request类型,想一下就知道它的作用肯定是告诉我们是命令还是数据,什么命令,什么数据等等,看一下它的定义:

struct mmc_request {
       struct mmc_command     *cmd;
       struct mmc_data              *data;
       struct mmc_command     *stop;
 
       void                     *done_data; /* completion data */
       void                     (*done)(struct mmc_request *);/* completion function */
};

很明了了吧,命令有命令的结构体描述(struct mmc_command),数据有数据的结构体描述(struct mmc_data),同时还有回调函数。

看一下s3c怎么利用传入的参数实现最终的操作的!

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; //表明我们不是stop命令
       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 //如果卡存在,那么我们就进行具体的send 请求
              s3cmci_send_request(mmc);
}

我们可以想一下,要通过控制器发送数据,一般的步骤? 我想一般都是操作控制器对应的寄存器吧! 这组寄存器的地址在probe里面已经申请并建立映射了,我们是可以直接拿来用的(host->base)。

s3cmci_send_request里面的具体实现:
如果命令附带有数据(表示是数据操作),那么先进行数据的初始化操作,这类操作稍后再谈,先谈命令的发送。
最终调用了s3cmci_send_commands3cmci_send_command主要的工作:
设置s3c中断寄存器使能一些中断,至于具体使能哪些可以对照代码和s3c手册。
设置s3c参数寄存器,这其实就是mmc/sd/sdio协议里面发送命令6字节里面的参数部分。
设置s3c命令控制寄存器,这主要设置命令码(比如由card层传来的)、是否等待响应、是否是长响应、启动命令的发送。

实际上到这一步,上层要求的命令或者数据请求就已经送出去了,只是最后的处理得根据具体情况来。
如果发送的是命令,且是有响应的命令,那么会在中断处理函数里面处理。
如果发送的是数据,那么该命令的发送会导致前面一步(就是我说稍后再谈的那个地方)填充到fifo的数据发送。
如果有超时,也是在中断处理里面处理。中断处理的注册在probe里面,它是根据具体平台的指示来注册相应的中断的。
request_irq(host->irq, s3cmci_irq, 0, DRIVER_NAME, host)
同时这里要额外说明的是中断的处理一般会用到下半部的机制
这里采用的是tasklet,在probe里面初始化的
tasklet_init(&host->pio_tasklet, pio_tasklet, (unsigned long) host);
所以我们应该猜到具体的读、写、错误处理都是在pio_tasklet里面,而中断处理函数s3cmci_irq里面一般就是读取寄存器状态,然后根据这些状态来具体最终的动作,当然动作就直接交给下半部处理了。
s3cmci_irq比较长,这里暂时不分析了,总之处理完一次请求我们都需要调用mmc_request_done,这是规定好的(毕竟请求里面有回调,这个函数是核心层实现的,它里面就调用了那个回调函数)。

第三部分、mmc驱动层

  Mmc层驱动实际上就实现了mmc/sd/sdio/三类设备的驱动。它具体的实现都在card目录下。

下面简单分析下block.c中的mmc_blk_init负责初始化mmc层。主要工作:
因为是向上层展示为块设备,所以第一步就是
res = register_blkdev(MMC_BLOCK_MAJOR, "mmc");申请了块设备号MMC_BLOCK_MAJOR(179),然后就将自己注册到mmc总线上去了,这个总线的初始化是在核心层的初始化中,上文有讲到。
res = mmc_register_driver(&mmc_driver);
这会导致与mmc总线上由控制器注册的设备匹配(至于控制器是怎么添加的后文在讲解),于是mmc_blk_probe函数会调用它的实现很简单:

  1. 分配一个上下文数据struct mmc_blk_data, 实际上该对象内嵌了块设备对象,同时也初始化了块队列处理函数和块操作集
  2. add_diskstruct mmc_blk_data内部已经初始化好的块设备对象添加的块子系统中

情景分析

下面分几个情景来讲述mmc子系统的一些细节实现

卡(mmc/sd/sdio)的热插拔处理

卡的热插拔的第一步初始化是在mmc_alloc_host中,也就是由核心层负责这事:

mmc_alloc_host
{
       ….
       INIT_DELAYED_WORK(&host->detect, mmc_rescan);
       …..
}

mmc_rescan负责扫描设备并添加设备,但是因为采用的中断方式触发扫描,所以同时还需要控制器的帮助,这就涉及到probe里面的:

if (!host->pdata->no_detect) {
              ret = gpio_request(host->pdata->gpio_detect, "s3cmci detect");
              if (ret) {
                     dev_err(&pdev->dev, "failed to get detect gpio\n");
                     goto probe_free_irq;
              }
 
              host->irq_cd = gpio_to_irq(host->pdata->gpio_detect);
 
              if (host->irq_cd >= 0) {
                     if (request_irq(host->irq_cd, s3cmci_irq_cd,
                                   IRQF_TRIGGER_RISING |
                                   IRQF_TRIGGER_FALLING,
                                   DRIVER_NAME, host)) {
                            dev_err(&pdev->dev,
                                   "can't get card detect irq.\n");
                            ret = -ENOENT;
                            goto probe_free_gpio_cd;
                     }
              } else {
                     dev_warn(&pdev->dev,
                             "host detect has no irq available\n");
                     gpio_direction_input(host->pdata->gpio_detect);
              }
       }
}

控制器从控制器平台设备端拿到监控相关的数据,比如哪个管脚。然后控制器为该管脚注册中断。
看一下s3cmci_irq_cd

static irqreturn_t s3cmci_irq_cd(int irq, void *dev_id)
{
       struct s3cmci_host *host = (struct s3cmci_host *)dev_id;
 
       dbg(host, dbg_irq, "card detect\n");
 
       mmc_detect_change(host->mmc, msecs_to_jiffies(500));
 
       return IRQ_HANDLED;
}

由此我们可以知道控制器监测管脚的中断处理很简单,只需要调用核心层指定的函数mmc_detect_change即可。至于内部的细节就不分析了,多说两句:里面通过工作队列异步触发工作者线程kmmcd(由核心层分配的),至于是什么工作呢这个就是上面说的mmc_rescan。上面说的就是热插拔的第一步,事件的触发监控。

第二步就是具体的热插拔处理流程了。
mmc_rescan的主要工作是(注意:这些都是核心层完成的,控制器驱动不用关心,但需要实现核心层要求的操作集,比如s3cmci_ops):

  1. 判断当前控制器上的卡是否之前已经存在,这种情况下只需要调用操作集里的监测函数:
if ((host->bus_ops != NULL) && host->bus_ops->detect && !host->bus_dead)
              host->bus_ops->detect(host);

这里的总线操作集bus_ops是在添加卡的时候赋值的,可以为

mmc_sdio_ops
mmc_sd_ops(mmc_sd_ops_unsafe)
mmc_ops

对于sd卡,就是mmc_sd_ops

static const struct mmc_bus_ops mmc_sd_ops = {
       .remove = mmc_sd_remove,
       .detect = mmc_sd_detect,
       .suspend = NULL,
       .resume = NULL,
       .power_restore = mmc_sd_power_restore,
};

这里面的实现就牵涉到具体的协议了,这里就不分析了。
2. 如果确实是卡存在,则退出,如果不是,则调用控制器注册的函数
s3cmci_ops里就是s3cmci_card_present,这个函数简单,就是读gpio管脚电平,不多分析了。然后进入到扫描最核心的部分:

       mmc_claim_host(host);//锁定控制器
 
       mmc_power_up(host);//控制器上电
       sdio_reset(host);
       mmc_go_idle(host); //进入空闲状态,这个是规范里面有的,怎么进入规范里面也有说明
 
       mmc_send_if_cond(host, host->ocr_avail); //发送控制器兼容的电压
 
       /*
        * First we search for SDIO...
        *///判断是否为sdio接口的设备
       err = mmc_send_io_op_cond(host, 0, &ocr);
       if (!err) {
              if (mmc_attach_sdio(host, ocr))
                     mmc_power_off(host);
              goto out;
       }
 
       /*
        * ...then normal SD...
        *///判断是否为sd卡
       err = mmc_send_app_op_cond(host, 0, &ocr);
       if (!err) {
              if (mmc_attach_sd(host, ocr))
                     mmc_power_off(host);
              goto out;
       }
 
       /*
        * ...and finally MMC.
        *///判断是否为mmc卡
       err = mmc_send_op_cond(host, 0, &ocr);
       if (!err) {
              if (mmc_attach_mmc(host, ocr))
                     mmc_power_off(host);
              goto out;
       }

还是以sd卡为例:
mmc_send_app_op_cond主要工作是:

  1. 发送命令41(该命令是sd支持的,但mmc卡不支持的命令
  2. 如果有正确响应,就代表是sd卡了,进入到mmc_attach_sd
    mmc_attach_sd的主要实现:
      设置sd卡的总线操作集:mmc_sd_attach_bus_ops(host); mmc_sd_init_card分配并初始化一个sd卡对象mmc_add_card 将sd卡对象添加的mmc总线这次的添加动作可能会引起card目录里面注册的mmc_driver的匹配,最终会在mmc_blk_probe实现向应用层提供块设备。

应用层读、写、控制卡流程

注意:这些都是核心层完成的,控制器驱动不用关心,但需要实现核心层要求的操作集,比如s3cmci_ops):

应用层一般都是操作基于sd卡块设备上的文件系统,所以会先经过vfs—>具体的文件系统的read、write-->块子系统submit_bio-->块请求队列处理函数即在card目录里面

mmc_blk_probe-->mmc_blk_allocàmmc_init_queueàmmc_request函数,该函数会唤醒队列处理线程mmcqd,mmcqd结合mmc_blk_issue_rq将上层的struct request_queue *q转为协议相关的请求,最终调用到控制器注册的 函数集里面的s3cmci_request

现在我们看控制器怎么实现请求的处理的:

  1. 调用s3cmci_send_request
    它会判断是否附带有数据的传输,如果是,那先对数据处理一下(比如如果是pio传输,那么采用pio方式将数据放到控制器fifo里去,如果是dma传输,则申请dma资源并配置好地址
  2. s3cmci_send_command触发命令的发送,这部分可以参考前文中的描述。

完!
2014年5月

posted @ 2017-10-14 10:19  rongpmcu  阅读(2553)  评论(0编辑  收藏  举报