2018-04-08 08:50:42 feixiaoxing 阅读数 1462
  • uboot的硬件驱动部分-2.10.uboot源码分析6

    本课程为uboot学习的第10部分,主要讲解uboot中的硬件驱动部分。主要以SD/MMC驱动为例详细分析了SD/MMC卡的初始化函数、读写函数等的逻辑,目的是让大家对驱动框架有所了解,进而通过此初步学习到linux驱动的思想。

    4585 人正在学习 去看看 朱有鹏

【 声明:版权所有,欢迎转载,请勿用于商业用途。 联系信箱:feixiaoxing @163.com


    随着sd卡的流行,sd卡在嵌入式设备上使用的场景也越来越多。那下面我们可以看一下,linux驱动框架上是怎么处理sd卡驱动的?


1、代码目录地址

drivers/mmc


2、基本结构

从mmc的代码结构可以看得出,主要分为两个部分,其中core为协议部分,host为各个soc的适配部分

host是我们需要真正关心的代码


3、以s3c为例,观察makefile

obj-$(CONFIG_MMC_SDHCI_S3C)	+= sdhci-s3c.o
......
obj-$(CONFIG_MMC_S3C)   	+= s3cmci.o

4、根据s3c阅读Kconfig,确认s3c下的sd卡还依赖哪些文件

config MMC_SDHCI_S3C
	tristate "SDHCI support on Samsung S3C SoC"
	depends on MMC_SDHCI && PLAT_SAMSUNG
	help
	  This selects the Secure Digital Host Controller Interface (SDHCI)
	  often referrered to as the HSMMC block in some of the Samsung S3C
	  range of SoC.

	  If you have a controller with this interface, say Y or M here.

	  If unsure, say N.

config MMC_SDHCI_S3C_DMA
	bool "DMA support on S3C SDHCI"
	depends on MMC_SDHCI_S3C
	help
	  Enable DMA support on the Samsung S3C SDHCI glue. The DMA
	  has proved to be problematic if the controller encounters
	  certain errors, and thus should be treated with care.

	  YMMV.

config MMC_S3C
	tristate "Samsung S3C SD/MMC Card Interface support"
	depends on ARCH_S3C24XX
	depends on S3C24XX_DMAC
	help
	  This selects a driver for the MCI interface found in
          Samsung's S3C2410, S3C2412, S3C2440, S3C2442 CPUs.
	  If you have a board based on one of those and a MMC/SD
	  slot, say Y or M here.

	  If unsure, say N.


5、MMC_S3C和MMC_SDHCI_S3C是主要的macro,接着了解两个macro对应文件的数据结构

首先看一下s3cmci.c文件,

static const struct mmc_host_ops s3cmci_ops = {
	.request	= s3cmci_request,
	.set_ios	= s3cmci_set_ios,
	.get_ro		= mmc_gpio_get_ro,
	.get_cd		= mmc_gpio_get_cd,
	.enable_sdio_irq = s3cmci_enable_sdio_irq,
};

再看一下sdhci-s3c.c文件,

static struct sdhci_ops sdhci_s3c_ops = {
	.get_max_clock		= sdhci_s3c_get_max_clk,
	.set_clock		= sdhci_s3c_set_clock,
	.get_min_clock		= sdhci_s3c_get_min_clock,
	.set_bus_width		= sdhci_set_bus_width,
	.reset			= sdhci_reset,
	.set_uhs_signaling	= sdhci_set_uhs_signaling,
};

6 、从host角度来说,有了platform driver和上面的host driver,驱动流程就算搞清楚了。

但是如果深入下去的话,还是需要好好读一下sd协议,这里面花的时间最多。



2014-09-06 11:05:47 Tiger99111 阅读数 817
  • uboot的硬件驱动部分-2.10.uboot源码分析6

    本课程为uboot学习的第10部分,主要讲解uboot中的硬件驱动部分。主要以SD/MMC驱动为例详细分析了SD/MMC卡的初始化函数、读写函数等的逻辑,目的是让大家对驱动框架有所了解,进而通过此初步学习到linux驱动的思想。

    4585 人正在学习 去看看 朱有鹏

Linux中对SD卡的支持非常完善了,我们只需要按照以下修改即可完成SD卡的初始化

1、arch/arm/mach-s3c2440/mach-smdk2440.c platform结构中增加&s3c_device_sdi语句。

static struct platform_device*smdk2440_devices[] __initdata =

{

&s3c_device_usb,

&s3c_device_lcd,

&s3c_device_wdt,

&s3c_device_i2c,

&s3c_device_iis,

&s3c_device_nand,

&s3c_device_sdi,                 //add here

};


2、添加和开发板相关的 SD 卡插拔引发的中断:
#vi  drivers/mmc/host/s3cmci.c +1335
在1335行,添加如下内容:

host->irq_cd = IRQ_EINT16;
s3c2410_gpio_cfgpin(S3C2410_GPG8, S3C2410_GPG8_EINT16);

注:SD卡中断管脚需根据硬件连接图实际情况修改


3、在makemenuconfig时选上所有关于sd卡的信息,要想支持中文还得选上:

Device Drivers --->

   <*> MMC/SD/SDIO card support --->

       --- MMC/SD/SDIO card support

       [ ] MMC debugging

       [ ] Allow unsafe resume (DANGEROUS)

       *** MMC/SD/SDIO Card Drivers ***

       <*> MMC block device driver

       [*] Use bounce buffer for simple hosts

       < > SDIO UART/GPS class support

        < > MMC host test driver

       *** MMC/SD/SDIO Host Controller Drivers ***

       < > Secure Digital Host Controller Interface support

       < > MMC/SD/SDIO over SPI

       <*> Samsung S3CSD/MMC Card Interface support

 

     File system--》

           DOS/FAT/NT Filesystems--》

                     VFAT(Windows-95)fs support

              (437)Default codepage for FAT

               **把437改为936

            Native language support -->

                 [*]Simplified Chinese charset

                 [*]NLS UTF-8

4、测试

        这样就可直接挂载了 mount -t vfat /dev/mmcblk0 /mnt

        如果sd卡分区了 mount -t vfat /dev/mmcblk0p1 /mnt

        直接读写sd就可以了

2012-04-28 18:11:51 Hens007 阅读数 9021
  • uboot的硬件驱动部分-2.10.uboot源码分析6

    本课程为uboot学习的第10部分,主要讲解uboot中的硬件驱动部分。主要以SD/MMC驱动为例详细分析了SD/MMC卡的初始化函数、读写函数等的逻辑,目的是让大家对驱动框架有所了解,进而通过此初步学习到linux驱动的思想。

    4585 人正在学习 去看看 朱有鹏

原文地址linux sd卡驱动分析 作者shark888


linux sd卡驱动分析 (2012-01-30 16:48)转载
标签:  linux  class  normal  color  Linux  分类: 驱动系列
基于S3C2410的SD卡linux驱动工作原理 
Linux MMC/SD/SDIO体系结构
linux sd卡驱动分析


我在讲嵌入式Linux驱动开发班的时候,发现不少学员对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读

 

 

 

了解了脱离操作系统的工作原理后,现在可以思考linux是如何管理管理SD卡的了。LinuxSD驱动可以分为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及数据传输的实现

 

 

              

            Linux MMC/SD/SDIO体系结构           
有关MMC/SD/SDIO相关的知识这里就不多讲了,请参考相关资料。这里主要涉及Linux下MMC相关内容。
内核版本(2.6.36)
首先说一下Linux相关MMC的代码分布,主要有两个目录,一个头文件目录和一个源代码目录。
分别位置如下:
include/linux/mmc
drivers/mmc
要阅读MMC相关代码就必须要看这两个目录。在drivers/mmc目录下分别有三个子目录,其将对应接下来要讲的MMC的体系结构。这三个子目录分别为:
 card
 core
 host
它们的内容后面讲到体系结构时自然就明了了。
 现在来说说MMC的体系结构,其分为三层
        /dev下设备文件访问MMC/SD/SDIO
用户空间             |
---------------------|-----------------------------------------------------
内核空间            \ /
         MMC Card层(对应具体的设备驱动,如MMC/SD卡块设备驱动,SDIO UART)
                     |
                    \ /
          MMC core层(为上次设备驱动实现提供操作接口,和下层host注册提供机制)
                     |
                    \ /
           Host层(具体MMC/SD/SDIO控制器驱动层。如S3C2440 MMC/SD控制器驱动)
                     |
                    \ /
-----------------------------------------------------------------------------
                    硬件层
对于我们来说,编写MMC/SD卡相关驱动主要涉及的就是Host层,其余层不用考虑。对于SDIO设备除了Host层以外,还有可能要编写MMC Card层的设备驱动。
编写Host层驱动,主要是填充mmc_host结构体相关内容和实现mmc_host_ops结构体中的函数。最后调用mmc_add_host向MMC core注册host驱动。可以参考S3C24XX的HOST驱动程序(drivers/mmc/host/s3cmci.c,s3cmci.h),上层MMC Core主要调用mmc_host_ops中的函数来实现与硬件交互。如下是mmc_host_ops结构体里面的函数:
  struct mmc_host_ops {
    /*使能和禁止HOST控制器*/
    int (*enable)(struct mmc_host *host);
    int (*disable)(struct mmc_host *host, int lazy);
    
        /*这个是关键的函数,所有对MMC/SD的操作,包括发命令和读数据,都通过该接口来实现,所以实现该接口时要处理是命令还是数据操作,另外要考虑是否使用DMA来进行数据传输。*/
    void    (*request)(struct mmc_host *host, struct mmc_request *req);
        /*用来设置MMC/SD的时钟,电压等操作*/
    void    (*set_ios)(struct mmc_host *host, struct mmc_ios *ios);
        /*检查MMC/SD是否写保护了*/
    int    (*get_ro)(struct mmc_host *host);
        /*检查mmc/sd的插入和拔出*/
    int    (*get_cd)(struct mmc_host *host);
    void    (*enable_sdio_irq)(struct mmc_host *host, int enable);
    /* optional callback for HC quirks */
    void    (*init_card)(struct mmc_host *host, struct mmc_card *card);
};

博客http://blog.chinaunix.net/u3/101649/showart.php?id=2232724对S3C2440的HOST驱动进行了详细的分析。
接下来说说MMC Core层。
该层主要实现了几个结构体函数指针,用来构建整个MMC设备驱动模型。它们是:
struct bus_type mmc_bus_type  /*mmc总线,用来管理sd/mmc卡设备和驱动*/
struct mmc_bus_ops mmc_ops    /*MMC卡总线操作函数,主要是在电源管理方面*/
struct mmc_bus_ops mmc_sd_op  /*SD卡总线操作函数,主要是在电源管理方面*/
struct mmc_bus_ops mmc_sdio_ops  /*SDIO总线操作函数,主要是在电源管理方面*/
struct bus_type sdio_bus_type  /*SDIO另外定义了一条总线*/
core.c文件中实现了几个关键的函数,用来提供给上层MMC Card调用和对SD/MMC卡的侦测函数以及初始化。
供给上层MMC Card调用主要有:
void mmc_wait_for_req(struct mmc_host *host, struct mmc_request *mrq);
int mmc_wait_for_cmd(struct mmc_host *host, struct mmc_command *cmd, int retries);
mmc card层就是通过这些函数来操作mmc/sd卡。而这些函数最终调用的是mmc_host_ops 结构体中的request函数来进行具体的操作。
对SD/MMC卡的侦测函数以及初始化,主要实现在
void mmc_rescan(struct work_struct *work);
函数中。
host层在调用mmc_add_host时会引发该函数的调用,侦测/初始化顺序为:
  先SDIO接口
  /*
     * First we search for SDIO...
     */
    err = mmc_send_io_op_cond(host, 0, &ocr);
    if (!err) {
        if (mmc_attach_sdio(host, ocr)) {
   ...
在SD:
  /*
     * ...then normal SD...
     */
    err = mmc_send_app_op_cond(host, 0, &ocr);
    if (!err) {
        if (mmc_attach_sd(host, ocr))
最后是MMC:
 /*
     * ...and finally MMC.
     */
    err = mmc_send_op_cond(host, 0, &ocr);
    if (!err) {
        if (mmc_attach_mmc(host, ocr))
其中mmc_attach_xxx函数就是用来完成侦测和初始化的,选择相应的总线操作函数,并产生struct mmc_card结构体,并填充其内容,最后注册一个mmc_card(代表着一个设备),并在注册中由mmc_bus_type结构体的match和probe函数来查找到适合该设备的驱动(这个又牵涉到设备驱动模型,可以查看设备驱动模型相关内容,了解设备和驱动匹配的过程),这里将匹配到mmc card层的MMC_Block(MMC块设备驱动程序,由 struct mmc_driver代表)。在完成设备侦测和初始化以后,以后的操作就是mmc card层中相关的设备驱动程序发出的了。

再说说MMC card层,该层主要实现具体的设备驱动程序,如MMC块设备驱动程序,通过mmc_register_driver注册。如果是SDIO就有可能是其它字符设备驱动程序了,其通过调用sdio_register_driver来注册设备驱动。
总体概括来说:
host层提供驱动相关MMC/SD/SDIO控制器的功能。
Core层提供了具体MMC/SD/SDIO设备侦测和初始化功能,以及电源管理方面的内容和通用的操作功能。
Card为实现具体的设备驱动层。
这样的分层结构在Linux设备驱动中非常常见,如I2C,SPI等都提供了这样的驱动模型。

最后简单说说SDIO相关部分。在core层注册了新的sdio_bus_type总线,并且定义了新的sdio_driver来代表sdio设备驱动,并定义了struct sdio_func来代表设备。所以在SDIO设备除了struct mmc_card来代表设备以外,还有struct sdio_func来代表具体功能设备。所以在mmc_attach_sdio函数中除了注册mmc_card以外,还注册了sdio_func。具体代码如下:
int mmc_attach_sdio(struct mmc_host *host, u32 ocr)
{
   ....
  /*
     * First add the card to the driver model...
     */
    err = mmc_add_card(host->card);
    if (err)
        goto remove_added;
    /*
     * ...then the SDIO functions.
     */
    for (i = 0;i < funcs;i++) {
        err = sdio_add_func(host->card->sdio_func[i]);
        if (err)
            goto remove_added;
    }
    ....
}
所以它除了调用mmc_bus_type结构体的match和probe函数来查找到适合该设备的驱动外,也调用sdio_bus_type结构体的match和probe函数来查找到适合该设备的驱动。   


      
                linux sd卡驱动分析
1. 硬件基础:
SD/MMC/SDIO 概念区分概要
SD (Secure Digital )与 MMC (Multimedia Card )
SD 是一种 flash memory card 的标准,也就是一般常见的 SD 记忆卡,而 MMC 则是较早的一种记忆卡标准,目前已经被 SD 标准所取代。
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 子系统的文档,在谷歌上搜索了很多,也没有找到相关文章。只能自己看代码分析了,可能有很多理解不对的地方,希望研究过这方面的朋友多邮件交流一下。
 
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 ", __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 中填充数据。当 64 字节的 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) ",
                         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 ", 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 部分的内容。
  、 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都不能通过,那这张卡恐怕就是无效卡了,初始化失败。

2016-04-02 10:18:21 zqixiao_09 阅读数 5512
  • uboot的硬件驱动部分-2.10.uboot源码分析6

    本课程为uboot学习的第10部分,主要讲解uboot中的硬件驱动部分。主要以SD/MMC驱动为例详细分析了SD/MMC卡的初始化函数、读写函数等的逻辑,目的是让大家对驱动框架有所了解,进而通过此初步学习到linux驱动的思想。

    4585 人正在学习 去看看 朱有鹏

      前面对SD卡控制器有了一个基本的介绍。其实SD控制器层更过的意义是为core层提供一种操作SD卡硬件的一种方法,当然不同的控制器对硬件控制的方法不尽相同,但是他们最终都能像core层提交一个统一的封装有操作方法的数据结构,那便是即将闪亮登场的struct mmc_host_ops....对应的host文件为s3cmci.c。

    接下来就来揭开与之对应的struct mmc_host_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,  
    .enable_sdio_irq = s3cmci_enable_sdio_irq,  
};  

       在讲述每个方法具体的实现之前,先来对struct mmc_host_ops结构中的各个成员有个简单的认识。

request方法:无论是前面所说的单纯的命令传输,还是带有数据的传输过程,无一例外最终都是调用request来实现的,那么如您所想,他也将成为这个舞台万众瞩目的焦点。

set_ios方法:用于设置SD卡控制器,前面我们所见到的设置控制器时钟,数据线宽度等等一系列操作最终就是通过他来实现的。

get_ro方法:获取卡的写保护状态,前面所过,SD卡初始化完成以后,我们进行的一个最后的工作便是检测卡的写保护状态,其实就是调用get_ro方法。

get_cd方法:检测卡是否在卡槽之中,它所对应的函数前面已经在初始化中分析过了,这里不再单独列来。

enable_sdio_irq方法:就是使能SDIO卡的中断,这个是对sdio卡而言的,这里将不做重点分析。

       有了一个初步的了解之后,接下来的时间就来各个击破了,本着由浅入深的原则我们先来看看s3cmci_get_ro。


一、s3cmci_get_ro

      从SD卡结构上来说有个写保护的开关,这就使得判断SD卡是否写保护可以从其机械特征上入手,从而特殊设计的SD卡槽为我们提供了方便。在这里采用的方法正是利用了这种特殊设计的SD卡槽带来的优势,因此只需要读取SD卡槽的SD写保护引脚的状态就能判定卡写保护的情况。实现的代码如下:

static int s3cmci_get_ro(struct mmc_host *mmc)
{
	struct s3cmci_host *host = mmc_priv(mmc);
	struct s3c24xx_mci_pdata *pdata = host->pdata;
	int ret;

	if (pdata->no_wprotect)
		return 0;

	ret = gpio_get_value(pdata->gpio_wprotect) ? 1 : 0;
	ret ^= pdata->wprotect_invert;

	return ret;
}

第10行正是获取SD写保护引脚的值,当然由于硬件设计上的不同可能带来状态上的取反,所以这里有个pdata->wprotect_invert标记决定是否应该反相。对于只读来说应该返回1,否则该方法的返回值为0。


二、s3cmci_set_ios

      根据我们前面所见到的种种设置,这里的ioset可能会相对烦锁一些,具体的代码如下:

static void s3cmci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
{
	struct s3cmci_host *host = mmc_priv(mmc);
	u32 mci_con;

	/* Set the power state */

	mci_con = readl(host->base + S3C2410_SDICON);

	switch (ios->power_mode) {
	case MMC_POWER_ON:
	case MMC_POWER_UP:
		/* Configure GPE5...GPE10 pins in SD mode */
		s3c_gpio_cfgall_range(S3C2410_GPE(5), 6, S3C_GPIO_SFN(2),
				      S3C_GPIO_PULL_NONE);

		if (host->pdata->set_power)
			host->pdata->set_power(ios->power_mode, ios->vdd);

		if (!host->is2440)
			mci_con |= S3C2410_SDICON_FIFORESET;

		break;

	case MMC_POWER_OFF:
	default:
		gpio_direction_output(S3C2410_GPE(5), 0);

		if (host->is2440)
			mci_con |= S3C2440_SDICON_SDRESET;

		if (host->pdata->set_power)
			host->pdata->set_power(ios->power_mode, ios->vdd);

		break;
	}

	s3cmci_set_clk(host, ios);

	/* Set CLOCK_ENABLE */
	if (ios->clock)
		mci_con |= S3C2410_SDICON_CLOCKTYPE;
	else
		mci_con &= ~S3C2410_SDICON_CLOCKTYPE;

	writel(mci_con, host->base + S3C2410_SDICON);

	if ((ios->power_mode == MMC_POWER_ON) ||
	    (ios->power_mode == MMC_POWER_UP)) {
		dbg(host, dbg_conf, "running at %lukHz (requested: %ukHz).\n",
			host->real_rate/1000, ios->clock/1000);
	} else {
		dbg(host, dbg_conf, "powered down.\n");
	}

	host->bus_width = ios->bus_width;
}

8行对SD卡控制器的设置最直接的莫过于对寄存器S3C2410_SDICON的访问了,为了保证后面不改变其他无关位的值,这里先读取S3C2410_SDICON中的当前值保存。

10-39行是SD控制器工作状态的设定,对ioset来说,swith无疑是他最好的朋友,当MMC_POWER_UP时,SD控制器的相应管脚会得到正确的初始化。其他的如fifo也将被正确复位。

41-47行就是对sd控制器时钟的设置,最终一切ioset的成功归功于49行将重新设置的S3C2410_SDICON状态写入寄存器,从此新的控制器状态生效。



        那么在主机驱动层中的一个请求处理是怎么通过核心层提交到块设备请求层的呢?

在网上找到一副图来说明他们之间的关联和处理流程,如下图:


三、s3cmci_request

       结构体mmc_request定义于/include/linux/mmc/core.h,它主要存放两大数据结构的指针,分别是cmddata,顾名思意,一个为指令,一个为数据

       也就是说,mmc_request结构体存放了进行主控制器与sd卡间通信所需要的指令和数据

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);
}

第9行判断SD卡是否还在卡槽之中,如果已经拔出,那不客气mmc_request_done将帮您结束这个请求。怎么个解决法还是先看看mmc_request_done的代码:

mmc_request_done

[core/core.c]

/**
 *	mmc_request_done - finish processing an MMC request
 *	@host: MMC host which completed request
 *	@mrq: MMC request which request
 *
 *	MMC drivers should call this function when they have completed
 *	their processing of a request.
 */
void mmc_request_done(struct mmc_host *host, struct mmc_request *mrq)
{
	struct mmc_command *cmd = mrq->cmd;
	int err = cmd->error;

	if (err && cmd->retries && mmc_host_is_spi(host)) {
		if (cmd->resp[0] & R1_SPI_ILLEGAL_COMMAND)
			cmd->retries = 0;
	}

	if (err && cmd->retries && !mmc_card_removed(host->card)) {
		/*
		 * Request starter must handle retries - see
		 * mmc_wait_for_req_done().
		 */
		if (mrq->done)
			mrq->done(mrq);
	} else {
		mmc_should_fail_request(host, mrq);

		led_trigger_event(host->led, LED_OFF);

		pr_debug("%s: req done (CMD%u): %d: %08x %08x %08x %08x\n",
			mmc_hostname(host), cmd->opcode, err,
			cmd->resp[0], cmd->resp[1],
			cmd->resp[2], cmd->resp[3]);

		if (mrq->data) {
			pr_debug("%s:     %d bytes transferred: %d\n",
				mmc_hostname(host),
				mrq->data->bytes_xfered, mrq->data->error);
		}

		if (mrq->stop) {
			pr_debug("%s:     (CMD%u): %d: %08x %08x %08x %08x\n",
				mmc_hostname(host), mrq->stop->opcode,
				mrq->stop->error,
				mrq->stop->resp[0], mrq->stop->resp[1],
				mrq->stop->resp[2], mrq->stop->resp[3]);
		}

		if (mrq->done)
			mrq->done(mrq);

		mmc_host_clk_release(host);
	}
}

14行如果是SPI传输出现错误,而且还有重试的机会,那么只要SPI不忽略这个命令,那么就还是给他重试的机会,也就到了85-91行继续调用host->ops->request(host, mrq);提交请求,否则既然是SPI忽略了这个命令,无论重试多少次都不会有结果,那么就干脆一不做二不休cmd->retries = 0;

14-16行就是只要设备有重生的机会就还是继续拯救...

26-51行如果传输无误或者重试次数到了,就会执行。其中多半是调试信息。

50-51行许下的承诺就好比欠下的债,前面我们讨论mmc_wait_for_req的时候有这么两句:

 mrq->done_data = &complete;
 mrq->done = mmc_wait_done;

然后我们说N年以后的某一天我们会和mmc_wait_done 再聚首,这里51 行便是调用的mmc_wait_done。内容如下:

mmc_wait_done

[core/core.c]

static void mmc_wait_done(struct mmc_request *mrq)
{
	complete(mrq->done_data);
}

     还记得mmc_wait_for_req中为了你苦苦等待的那个wait_for_completion(&complete),因为等待,所以她进入了睡眠。现在事情做完了,他重新回来调用complete(mrq->done_data)唤醒这个沉睡的内核精灵。说到这好奇的人难免会问,那要是一直出错又该是谁来唤醒他呢?带着疑问我们继续向前....   

    回到s3cmci_request,如果卡还存在的话s3cmci_send_request将真正开始这个请求的处理。

s3cmci_send_request

[host/s3cmci.c]

static void s3cmci_send_request(struct mmc_host *mmc)
{
	struct s3cmci_host *host = mmc_priv(mmc);
	struct mmc_request *mrq = host->mrq;
	struct mmc_command *cmd = host->cmd_is_stop ? mrq->stop : mrq->cmd;

	host->ccnt++;
	prepare_dbgmsg(host, cmd, host->cmd_is_stop);

	/* Clear command, data and fifo status registers
	   Fifo clear only necessary on 2440, but doesn't hurt on 2410
	*/
	writel(0xFFFFFFFF, host->base + S3C2410_SDICMDSTAT);
	writel(0xFFFFFFFF, host->base + S3C2410_SDIDSTA);
	writel(0xFFFFFFFF, host->base + S3C2410_SDIFSTA);

	if (cmd->data) {
		int res = s3cmci_setup_data(host, cmd->data);

		host->dcnt++;

		if (res) {
			dbg(host, dbg_err, "setup data error %d\n", res);
			cmd->error = res;
			cmd->data->error = res;

			mmc_request_done(mmc, mrq);
			return;
		}

		if (s3cmci_host_usedma(host))
			res = s3cmci_prepare_dma(host, cmd->data);
		else
			res = s3cmci_prepare_pio(host, cmd->data);

		if (res) {
			dbg(host, dbg_err, "data prepare error %d\n", res);
			cmd->error = res;
			cmd->data->error = res;

			mmc_request_done(mmc, mrq);
			return;
		}
	}

	/* Send command */
	s3cmci_send_command(host, cmd);

	/* Enable Interrupt */
	s3cmci_enable_irq(host, true);
}

13-15行全部写入1,是为了清除之前传输的SDI命令状态寄存器、SDI数据状态寄存器以及SDI FIFO状态寄存器。这是在一次新的传输之前所必须有的初始化工作,否则可能出现未知的状态错误。

17行cmd->data实际上就是mmc_request->data,前面没少对他进行介绍。与之相类似的还有stop->data。这里我们姑且不说带有数据的传输过程,先来看看SD卡命令的实现。也就是1171行s3cmci_send_command(host, cmd);


命令、数据发送流程如下图:


1、发送命令

s3cmci_send_command(host, cmd)

[host/s3cmci.c]

static void s3cmci_send_command(struct s3cmci_host *host,
					struct mmc_command *cmd)
{
	u32 ccon, imsk;

	imsk  = S3C2410_SDIIMSK_CRCSTATUS | S3C2410_SDIIMSK_CMDTIMEOUT |
		S3C2410_SDIIMSK_RESPONSEND | S3C2410_SDIIMSK_CMDSENT |
		S3C2410_SDIIMSK_RESPONSECRC;

	enable_imask(host, imsk);

	if (cmd->data)
		host->complete_what = COMPLETION_XFERFINISH_RSPFIN;
	else if (cmd->flags & MMC_RSP_PRESENT)
		host->complete_what = COMPLETION_RSPFIN;
	else
		host->complete_what = COMPLETION_CMDSENT;

	writel(cmd->arg, host->base + S3C2410_SDICMDARG);

	ccon  = cmd->opcode & S3C2410_SDICMDCON_INDEX;
	ccon |= S3C2410_SDICMDCON_SENDERHOST | S3C2410_SDICMDCON_CMDSTART;

	if (cmd->flags & MMC_RSP_PRESENT)
		ccon |= S3C2410_SDICMDCON_WAITRSP;

	if (cmd->flags & MMC_RSP_136)
		ccon |= S3C2410_SDICMDCON_LONGRSP;

	writel(ccon, host->base + S3C2410_SDICMDCON);
}

6-8行是使能相应的中断,其中包括CRC校验错误、命令超时、收到命令响应等等。具体的中断屏蔽寄存器的内容可以参考S3C2440用户手册。

12-17行实际上指当前的这个命令结束时候应该所处的状态,中断处理函数将实际硬件的完成情况与这个状态相比较,最终得到这个命令执行的结果。而cmd->flags正是前面提交命令之前根据不同命令的实际情况来设置的,比如具有应答数据的命令可能需要设置cmd->flags |= MMC_RSP_PRESENT。然后对应的结束状态也就应该是COMPLETION_RSPFIN收到应答。前面说过host->complete_what是个枚举类型的变量包含了整个命令过程的各个阶段,内容如下:

enum s3cmci_waitfor {  
    COMPLETION_NONE,  
    COMPLETION_FINALIZE,  
    COMPLETION_CMDSENT,  
    COMPLETION_RSPFIN,  
    COMPLETION_XFERFINISH,  
    COMPLETION_XFERFINISH_RSPFIN,  
};  

一般的命令可能无应答阶段,我们默认数据传输正确完成以后即认为命令执行完成也就是17行对应的host->complete_what = COMPLETION_CMDSENT;

19行是对命令命令参数寄存器的设置,cmd->arg是一个32bit的整数,这里如实填写即可。

21行之后的内容就是对控制寄存器的设置了,由于控制寄存器比较重要,这里列出他寄存器位的信息如下:对照上表应该不难分析函数中所设置的每一位的具体含义,这里就不再一一解释了。SDICmdCon[8]的置位使得SD控制器开始发送命令。回到s3cmci_send_request....

前一段第50行s3cmci_enable_irq(host, true); 就是使能SDI 控制器的中断。然而s3cmci_send_command 中间的944 行设置imr|=S3C2410_SDIIMSK_CMDSENT,命中注定命令发出以后产生一个相应的中断,接下来就进入probe 阶段所注册的那个SDI 中断request_irq(host->irq, s3cmci_irq, 0, DRIVER_NAME, host)

request_irq(host->irq, s3cmci_irq, 0, DRIVER_NAME, host)

[host/s3cmci.c]

/*
 * ISR for SDI Interface IRQ
 * Communication between driver and ISR works as follows:
 *   host->mrq 			points to current request
 *   host->complete_what	Indicates when the request is considered done
 *     COMPLETION_CMDSENT	  when the command was sent
 *     COMPLETION_RSPFIN          when a response was received
 *     COMPLETION_XFERFINISH	  when the data transfer is finished
 *     COMPLETION_XFERFINISH_RSPFIN both of the above.
 *   host->complete_request	is the completion-object the driver waits for
 *
 * 1) Driver sets up host->mrq and host->complete_what
 * 2) Driver prepares the transfer
 * 3) Driver enables interrupts
 * 4) Driver starts transfer
 * 5) Driver waits for host->complete_rquest
 * 6) ISR checks for request status (errors and success)
 * 6) ISR sets host->mrq->cmd->error and host->mrq->data->error
 * 7) ISR completes host->complete_request
 * 8) ISR disables interrupts
 * 9) Driver wakes up and takes care of the request
 *
 * Note: "->error"-fields are expected to be set to 0 before the request
 *       was issued by mmc.c - therefore they are only set, when an error
 *       contition comes up
 */

static irqreturn_t s3cmci_irq(int irq, void *dev_id)
{
	struct s3cmci_host *host = dev_id;
	struct mmc_command *cmd;
	u32 mci_csta, mci_dsta, mci_fsta, mci_dcnt, mci_imsk;
	u32 mci_cclear = 0, mci_dclear;
	unsigned long iflags;

	mci_dsta = readl(host->base + S3C2410_SDIDSTA);
	mci_imsk = readl(host->base + host->sdiimsk);

	if (mci_dsta & S3C2410_SDIDSTA_SDIOIRQDETECT) {
		if (mci_imsk & S3C2410_SDIIMSK_SDIOIRQ) {
			mci_dclear = S3C2410_SDIDSTA_SDIOIRQDETECT;
			writel(mci_dclear, host->base + S3C2410_SDIDSTA);

			mmc_signal_sdio_irq(host->mmc);
			return IRQ_HANDLED;
		}
	}

	spin_lock_irqsave(&host->complete_lock, iflags);

	mci_csta = readl(host->base + S3C2410_SDICMDSTAT);
	mci_dcnt = readl(host->base + S3C2410_SDIDCNT);
	mci_fsta = readl(host->base + S3C2410_SDIFSTA);
	mci_dclear = 0;

	if ((host->complete_what == COMPLETION_NONE) ||
	    (host->complete_what == COMPLETION_FINALIZE)) {
		host->status = "nothing to complete";
		clear_imask(host);
		goto irq_out;
	}

	if (!host->mrq) {
		host->status = "no active mrq";
		clear_imask(host);
		goto irq_out;
	}

	cmd = host->cmd_is_stop ? host->mrq->stop : host->mrq->cmd;

	if (!cmd) {
		host->status = "no active cmd";
		clear_imask(host);
		goto irq_out;
	}

	if (!s3cmci_host_usedma(host)) {
		if ((host->pio_active == XFER_WRITE) &&
		    (mci_fsta & S3C2410_SDIFSTA_TFDET)) {

			disable_imask(host, S3C2410_SDIIMSK_TXFIFOHALF);
			tasklet_schedule(&host->pio_tasklet);
			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";
		}
	}

	if (mci_csta & S3C2410_SDICMDSTAT_CMDTIMEOUT) {
		dbg(host, dbg_err, "CMDSTAT: error CMDTIMEOUT\n");
		cmd->error = -ETIMEDOUT;
		host->status = "error: command timeout";
		goto fail_transfer;
	}

	if (mci_csta & S3C2410_SDICMDSTAT_CMDSENT) {
		if (host->complete_what == COMPLETION_CMDSENT) {
			host->status = "ok: command sent";
			goto close_transfer;
		}

		mci_cclear |= S3C2410_SDICMDSTAT_CMDSENT;
	}

	if (mci_csta & S3C2410_SDICMDSTAT_CRCFAIL) {
		if (cmd->flags & MMC_RSP_CRC) {
			if (host->mrq->cmd->flags & MMC_RSP_136) {
				dbg(host, dbg_irq,
				    "fixup: ignore CRC fail with long rsp\n");
			} else {
				/* note, we used to fail the transfer
				 * here, but it seems that this is just
				 * the hardware getting it wrong.
				 *
				 * cmd->error = -EILSEQ;
				 * host->status = "error: bad command crc";
				 * goto fail_transfer;
				*/
			}
		}

		mci_cclear |= S3C2410_SDICMDSTAT_CRCFAIL;
	}

	if (mci_csta & S3C2410_SDICMDSTAT_RSPFIN) {
		if (host->complete_what == COMPLETION_RSPFIN) {
			host->status = "ok: command response received";
			goto close_transfer;
		}

		if (host->complete_what == COMPLETION_XFERFINISH_RSPFIN)
			host->complete_what = COMPLETION_XFERFINISH;

		mci_cclear |= S3C2410_SDICMDSTAT_RSPFIN;
	}

	/* errors handled after this point are only relevant
	   when a data transfer is in progress */

	if (!cmd->data)
		goto clear_status_bits;

	/* Check for FIFO failure */
	if (host->is2440) {
		if (mci_fsta & S3C2440_SDIFSTA_FIFOFAIL) {
			dbg(host, dbg_err, "FIFO failure\n");
			host->mrq->data->error = -EILSEQ;
			host->status = "error: 2440 fifo failure";
			goto fail_transfer;
		}
	} else {
		if (mci_dsta & S3C2410_SDIDSTA_FIFOFAIL) {
			dbg(host, dbg_err, "FIFO failure\n");
			cmd->data->error = -EILSEQ;
			host->status = "error:  fifo failure";
			goto fail_transfer;
		}
	}

	if (mci_dsta & S3C2410_SDIDSTA_RXCRCFAIL) {
		dbg(host, dbg_err, "bad data crc (outgoing)\n");
		cmd->data->error = -EILSEQ;
		host->status = "error: bad data crc (outgoing)";
		goto fail_transfer;
	}

	if (mci_dsta & S3C2410_SDIDSTA_CRCFAIL) {
		dbg(host, dbg_err, "bad data crc (incoming)\n");
		cmd->data->error = -EILSEQ;
		host->status = "error: bad data crc (incoming)";
		goto fail_transfer;
	}

	if (mci_dsta & S3C2410_SDIDSTA_DATATIMEOUT) {
		dbg(host, dbg_err, "data timeout\n");
		cmd->data->error = -ETIMEDOUT;
		host->status = "error: data timeout";
		goto fail_transfer;
	}

	if (mci_dsta & S3C2410_SDIDSTA_XFERFINISH) {
		if (host->complete_what == COMPLETION_XFERFINISH) {
			host->status = "ok: data transfer completed";
			goto close_transfer;
		}

		if (host->complete_what == COMPLETION_XFERFINISH_RSPFIN)
			host->complete_what = COMPLETION_RSPFIN;

		mci_dclear |= S3C2410_SDIDSTA_XFERFINISH;
	}

clear_status_bits:
	writel(mci_cclear, host->base + S3C2410_SDICMDSTAT);
	writel(mci_dclear, host->base + S3C2410_SDIDSTA);

	goto irq_out;

fail_transfer:
	host->pio_active = XFER_NONE;

close_transfer:
	host->complete_what = COMPLETION_FINALIZE;

	clear_imask(host);
	tasklet_schedule(&host->pio_tasklet);

	goto irq_out;

irq_out:
	dbg(host, dbg_irq,
	    "csta:0x%08x dsta:0x%08x fsta:0x%08x dcnt:0x%08x status:%s.\n",
	    mci_csta, mci_dsta, mci_fsta, mci_dcnt, host->status);

	spin_unlock_irqrestore(&host->complete_lock, iflags);
	return IRQ_HANDLED;

}

36-48行是判断SDIO所触发的中断,与我们说说的无关。飘过....

30-34行分别读取命令状态、尚未完成传输的数据大小以及FIFO的状态的值。

56-61行就是之前所分析的host->complete_what,如果设备无欲无求host->complete_what == COMPLETION_NONE,即使连最基本的命令发送也不要求完成的话,那就没什么意义了,直接清除IMASK,返回。

static inline void clear_imask(struct s3cmci_host *host)
{
	u32 mask = readl(host->base + host->sdiimsk);

	/* preserve the SDIO IRQ mask state */
	mask &= S3C2410_SDIIMSK_SDIOIRQ;
	writel(mask, host->base + host->sdiimsk);
}

上面的代码只保留了SDIO IRQ状态,其他的中断都是被屏蔽了的。由此足见其对SDIO设备的偏心程度。

585-589 行尽然玩丢了host->mrq,无论是命令还是数据请求,我们都是递交了struct mmc_request结构的,所以驱动很气愤,直接返回。

591行前面我们看到struct mmc_request中包含了两种类型的struct mmc_cmd一个是所谓的cmd另外一个就是stop了,当然选择哪一个也不是他自己说来算了,当然有主机host-
>cmd_is_stop来决定了。

63-67行是PIO模式下数据传输的,我们姑且先放着,等说完CMD回头再看。

98-103行命令超时以后就会跳转到fail_transfer,至于fail_transfer又干了些啥好事我们走到那里了再说,继续前进...

105-109行命令发送成功以后所产生的中断,如果host->complete_what也正好只要求传输成功即COMPLETION_CMDSENT,那正好完成工作,goto close_transfer。

114-129行是CRC错误,忽略。

134-138行命令相应接收成功,那么依旧goto close_transfer。

140-143行至今尚未发现一个所谓的COMPLETION_XFERFINISH_RSPFIN最多也就数据传输成功那么修改一下这个脑残host->complete_what =COMPLETION_XFERFINISH;

149-150行如果没有数据传输,那么接下来就可以进行状态清理工作了。

153-200行是检查FIFO信息的,回头说到PIO传输的时候在来分析它。

203-204行意图很明确,显然是毁尸灭迹,清除状态。最后可以看到无论是先前的fail_transfer:还是后来的close_transfer,最总都会去调用

215行的tasklet_schedule(&host->pio_tasklet),是什么赋予这个函数如此强大的魅力,且听下回分解...


2、数据传输 s3cmci_setup_data

        是时候该看点实际的数据传输了,前面说过s3cmci_send_request中的if (cmd->data)是区分命令是否有数据阶段的关键标志。如果有数据传输的,那么就到了
s3cmci_setup_data

static int s3cmci_setup_data(struct s3cmci_host *host, struct mmc_data *data)
{
	u32 dcon, imsk, stoptries = 3;

	/* write DCON register */

	if (!data) {
		writel(0, host->base + S3C2410_SDIDCON);
		return 0;
	}

	if ((data->blksz & 3) != 0) {
		/* We cannot deal with unaligned blocks with more than
		 * one block being transferred. */

		if (data->blocks > 1) {
			pr_warning("%s: can't do non-word sized block transfers (blksz %d)\n", __func__, data->blksz);
			return -EINVAL;
		}
	}

	while (readl(host->base + S3C2410_SDIDSTA) &
	       (S3C2410_SDIDSTA_TXDATAON | S3C2410_SDIDSTA_RXDATAON)) {

		dbg(host, dbg_err,
		    "mci_setup_data() transfer stillin progress.\n");

		writel(S3C2410_SDIDCON_STOP, host->base + S3C2410_SDIDCON);
		s3cmci_reset(host);

		if ((stoptries--) == 0) {
			dbg_dumpregs(host, "DRF");
			return -EINVAL;
		}
	}

	dcon  = data->blocks & S3C2410_SDIDCON_BLKNUM_MASK;

	if (s3cmci_host_usedma(host))
		dcon |= S3C2410_SDIDCON_DMAEN;

	if (host->bus_width == MMC_BUS_WIDTH_4)
		dcon |= S3C2410_SDIDCON_WIDEBUS;

	if (!(data->flags & MMC_DATA_STREAM))
		dcon |= S3C2410_SDIDCON_BLOCKMODE;

	if (data->flags & MMC_DATA_WRITE) {
		dcon |= S3C2410_SDIDCON_TXAFTERRESP;
		dcon |= S3C2410_SDIDCON_XFER_TXSTART;
	}

	if (data->flags & MMC_DATA_READ) {
		dcon |= S3C2410_SDIDCON_RXAFTERCMD;
		dcon |= S3C2410_SDIDCON_XFER_RXSTART;
	}

	if (host->is2440) {
		dcon |= S3C2440_SDIDCON_DS_WORD;
		dcon |= S3C2440_SDIDCON_DATSTART;
	}

	writel(dcon, host->base + S3C2410_SDIDCON);

	/* write BSIZE register */

	writel(data->blksz, host->base + S3C2410_SDIBSIZE);

	/* add to IMASK register */
	imsk = S3C2410_SDIIMSK_FIFOFAIL | S3C2410_SDIIMSK_DATACRC |
	       S3C2410_SDIIMSK_DATATIMEOUT | S3C2410_SDIIMSK_DATAFINISH;

	enable_imask(host, imsk);

	/* write TIMER register */

	if (host->is2440) {
		writel(0x007FFFFF, host->base + S3C2410_SDITIMER);
	} else {
		writel(0x0000FFFF, host->base + S3C2410_SDITIMER);

		/* FIX: set slow clock to prevent timeouts on read */
		if (data->flags & MMC_DATA_READ)
			writel(0xFF, host->base + S3C2410_SDIPRE);
	}

	return 0;
}

7-10行如果data不存在,接下来就无事可做了。

12-20行块大小是4字节对齐的,如果data->blksz不满足,那么返回错误。

22-35行读取数据状态寄存器,如果正在发送或接收数据,则s3cmci_reset(host);复位SD控制器。

static void s3cmci_reset(struct s3cmci_host *host)
{
	u32 con = readl(host->base + S3C2410_SDICON);
	
	con |= S3C2440_SDICON_SDRESET;
	writel(con, host->base + S3C2410_SDICON);
}

37-63行根据数据特征、主机总线宽度等信息设置数据控制寄存器。

67行设置SD控制器块大小寄存器。这是上层设置下来的值,一般为512。

70-73行设置中断屏蔽寄存器,使能数据传输完成中断、超时等。

77-84行是关于读写超时的处理。接着返回到s3cmci_send_request....

如果不出什么问题,应该就到了85行。

s3cmci_prepare_dma(host, cmd->data);是DMA传输的处理,

s3cmci_prepare_pio(host, cmd->data);是PIO方式的处理,下面我们先来关注PIO方式的数据传输。



2010-04-21 13:18:00 devehe 阅读数 680
  • uboot的硬件驱动部分-2.10.uboot源码分析6

    本课程为uboot学习的第10部分,主要讲解uboot中的硬件驱动部分。主要以SD/MMC驱动为例详细分析了SD/MMC卡的初始化函数、读写函数等的逻辑,目的是让大家对驱动框架有所了解,进而通过此初步学习到linux驱动的思想。

    4585 人正在学习 去看看 朱有鹏

 

http://blog.csdn.net/lanmanck/archive/2009/11/30/4903559.aspx

 

linux sd卡驱动分析,基于mini2440,sdio mmc sd卡驱动编写 收藏

最近要让s3c2440在linux2.6.18上 支持4G的SD卡.

原文地址:http://linux4u.wikidot.com/mmc-controller-driver

==============================================================

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都不能通过,那这张卡恐怕就是无效卡了,初始化失败。

 

 

 

 

 

 

没有更多推荐了,返回首页