zoukankan      html  css  js  c++  java
  • 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.35driversmmc下,如下图
    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
    ", __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
    ");
                         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.
    ");
                                ret = -ENOENT;
                                goto probe_free_gpio_cd;
                         }
                  } else {
                         dev_warn(&pdev->dev,
                                 "host detect has no irq available
    ");
                         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
    ");
     
           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月

  • 相关阅读:
    ssh的配置[待写]
    回文检测题解
    叠积木/银河系英雄传说[NOI2002]题解
    酒店之王解题报告
    摆花解题报告
    摆渡车(noip2018 pj t3)
    NOIP2018 普及 凉凉记
    子矩阵(NOIP2014T4)
    花店橱窗布置(洛谷:P1854)
    我想大声告诉你
  • 原文地址:https://www.cnblogs.com/rongpmcu/p/7662696.html
Copyright © 2011-2022 走看看