qualcomm MMC子系统

一 

        本mmc子系统主要讨论了下述内容:

        关于mmc子系统,各硬件平台、各Linux版本之间的差异;

        阅读mmc子系统需要的知识准备;

        啥是是mmc,啥又是SD,啥又是SDIO;

        一些LInux设备模型的重现;

        虚拟总线;

        mmc子系统的结构组织;

        关于platform总线,研究的主线一;

        关于mmc总线,研究的主线二;

        关于sdio总线

        相关结构体;比如 platform_device描述一个platform设备,platform driver描述一个msmsdcc_driver设备驱动,描述mmc card的结构等等;

        host控制的结构体;

        mmc子系统的代码组织;

        BSP相关代码;

        驱动相关代码;

        主要代码分析;

        mmc card初始设备;

         相关中断,及上下半步各完成什么工作;

        定时器相关的一些介绍;


mmc子系统是Linux设备驱动中一个不可缺少的部分,但科技进步,时代发展,现在mmc已经不是从前的mmc(Multimedia Card),现在mmc子系统已经衍生到SD,SDIO相关的技术了,也就是说mmc子系统已经能够管理控制SD和SDIO相关了,但是由于历史的原因,仍称呼为mmc子系统。

mmc子系统差异说明:

        本mmc子系统学习笔记一以htc g5(android)手机的linux内核为例,除设备资源外的差别之外,和其他linux内核无异。

       内核版本:2.6.x

        linux设备驱动多分为多层次管理,只有最底层一般会和设备资源相关,故mmc子系统的最底层才与不同厂商打交道,如s3c,msm,omap等,但是处理流程一样,无多少变化。

 关于mmc

         MMC:Multi Media Card的缩写,即多媒体卡。24mm*32mm*1.mm。以前的MMC规范的数据传输宽度只有1位,最新的1.0版MMC中拓宽了4位和8位带宽,时钟频率也达到了52MHZ,从而理所当然的支持50MHZ的传输速率。值得一提的是,对于SD时代提倡的“数据安全”特性,MMC协会也终于接纳了具有竞争性的安全卡协议——Secure MMC1.1规范。 

关于SD,SDIO

         SD(Secure Digital Memory Card),即安全数码存储卡,建议读法是SD存储卡。它在MMC的基础上发展而来,并且增加了相对于MMC的两个新特色:传输速率比2.11版本的MMC快了将近4倍,另外SD存储卡强调数据的安全,可以设定数据的使用权限,防止他人复制。

        在数据和物理规范上,SD存储卡向前兼容MMC卡,在外观上,SD存储卡只是在厚度上比MMC卡厚了0.7mm。

  这段时间在研究SDIO接口,所以要求对MMC、SD、SDIO都要有所了解,网上找过这些概念,不过总是流传的是Jollen 笔记里面的一份,许多地方未能清楚交待,所以我在其基础上加上一些自己的了解来加深对SDIO的学习。并一并学习下SDIO总线。


一、首先从SD和MMC说起
MMC(Multimedia Card)与 SD(Secure Digital Memory Card)
MMC:MultiMedia Card的缩写,即多媒体卡。24mm*32mm*1.mm。以前的MMC规范的数据传输宽度只有1位,最新的1.0版MMC中拓宽了4位和8位带宽,时钟频率也达到了52MHZ,从而理所当然的支持50MHZ的传输速率。值得一提的是,对于SD时代提倡的“数据安全”特性,MMC协会也终于接纳了具有竞争性的安全卡协议——Secure MMC1.1规范。
SD(Secure Digital Memory Card),即安全数码存储卡,我建议的读法是SD存储卡。它在MMMC的基础上发展而来,并且增加了相对于MMC的两个新特色:传输速率比2.11版本的MMC快了将近4倍,另外SD存储卡强调数据的安全,可以设定数据的使用权限,防止他人复制。
        所以可以这样讲SD 是一种 flash memory card 的标准,也就是一般常见的 SD 记忆卡,而 MMC 则是较早的一种记忆卡标准,目前已经被 SD 标准所取代。在数据和物理规范上,SD存储卡向前兼容MMC卡,在外观上,SD存储卡只是在厚度上比MMC卡厚了0.7mm。
二、SDIO(Secure Digital I/O)概述
SDIO 是目前我们比较关心的技术,SDIO 故名思义,就是 SD 的 I/O 接口(interface)的意思,不过这样解释可能还有点抽像。更具体的说明,SD 本来是记忆卡的标准,但是现在也可以把 SD 拿来插上一些外围接口使用,这样的技术便是 SDIO。
所以 SDIO 本身是一种相当单纯的技术,透过 SD 的 I/O 接脚来连接外部外围,并且透过 SD 上的 I/O 数据接位与这些外围传输数据,而且 SD 协会会员也推出很完整的 SDIO stack 驱动程序,使得 SDIO 外围(我们称为 SDIO 卡)的开发与应用变得相当热门。
现在已经有非常多的手机或是手持装置都支持 SDIO 的功能(SD 标准原本就是针对 mobile device 而制定),而且许多 SDIO 外围也都被开发出来,让手机外接外围更加容易,并且开发上更有弹性(不需要内建外围)。目前常见的 SDIO 外围(SDIO 卡)有:Wi-Fi card(无线网络卡)、CMOS sensor card(照相模块)、GPS card、GSM/GPRS modem card、Bluetooth card、Radio/TV card SDIO 的应用将是未来嵌入式系统最重要的接口技术之一,并且也会取代目前 GPIO 式的 SPI 接口。
三、SDIO总线协议
SDIO协议是由SD卡的协议演化升级而来的,很多地方保留了SD卡的读写协议,同时SDIO协议又在SD卡协议之上添加了CMD52和CMD53命令。 由于这个,SDIO和SD卡规范间的
一个重要区别是增加了低速标准,低速卡的目标应用是以最小的硬件开始来支持低速I/O能力。低速卡支持类似调制解调 器,条形码扫描仪和GPS接收器等应用。高速卡支持网卡,电视卡还有“组合”(combo)卡等,组合卡指的是存储器+SDIO。
       SDIO和SD卡的SPEC间的又一个重要区别是增加了低速标准。SDIO卡只需要SPI和1位SD传输模式。低速卡的目标应用是以最小的硬件开支来支持低速I/O能力,低速卡支持如MODEM,条形扫描仪和GPS接收器等应用。对组合卡来说,全速和4BIT操作对卡内存储器和SDIO部分都是强制要求 的。
       在非组合卡的SDIO设备里,其最高速度要只有达到25M,而组合卡的最高速度同SD卡的最高速度一样,要高于25M。
       SDIO总线和USB总线类似,SDIO总线也有两端,其中一端是主机(HOST)端,另一端是设备端(DEVICE),采用HOST-DEVICE这样的设计是为了简化DEVICE的设计,所有的通信都是由HOST端发出命令开始的。在DEVICE端只要能解溪HOST的命令,就可以同 HOST进行通信了。
        SDIO的HOST可以连接多个DEVICE,这个是同SD的总线一样的,其中有如下的几种信号:
1.CLK信号:HOST给DEVICE的时钟信号.
2.CMD信号:双向的信号,用于传送命令和反应。
3.DAT0-DAT3 信号:四条用于传送的数据线。
4.VDD信号:电源信号。
5.VSS1,VSS2:电源地信号。
在SDIO总线定义中,DAT1信号线复用为中断线。在SDIO的1BIT模式下DAT0用来传输数据,DAT1用作中断线。在SDIO的4BIT模式下DAT0-DAT3用来传输数据,其中DAT1复用作中断线。
四、SDIO命令
       SDIO总线上都是HOST端发起请求,然后DEVICE端回应请求。其中请求和回应中会数据信息。
1.Command:用于开始传输的命令,是由HOST端发往DEVICE端的。其中命令是通过CMD信号线传送的。
2.Response:回应是DEVICE返回的HOST的命令,作为Command的回应。也是通过CMD线传送的。
3.Data:数据是双向的传送的。可以设置为1线模式,也可以设置为4线模式。数据是通过DAT0-DAT3信号线传输的。
        SDIO的每次操作都是由HOST在CMD线上发起一个CMD,对于有的CMD,DEVICE需要返回Response,有的则不需要。
        对于读命令,首先HOST会向DEVICE发送命令,紧接着DEVICE会返回一个握手信号,此时,当HOST收到回应的握手信号后,会将数据放在4位的 数据线上,在传送数据的同时会跟随着CRC校验码。当整个读传送完毕后,HOST会再次发送一个命令,通知DEVICE操作完毕,DEVICE同时会返回 一个响应。
       对于写命令,首先HOST会向DEVICE发送命令,紧接着DEVICE会返回一个握手信号,此时,当HOST收到回应的握手信号后,会将数据放在4位的 数据线上,在传送数据的同时会跟随着CRC校验码。当整个写传送完毕后,HOST会再次发送一个命令,通知DEVICE操作完毕,DEVICE同时会返回 一个响应。
五、SDIO的寄存器
      SDIO卡的设备驱动80%的任务就是操作SDIO卡上的有关寄存器。SDIO卡最多允许有7个功能(function),这个同其功能号是对应的(0~7),每个功能都对应一个128K字节大小的寄存器。功能号之所以取值范围是1~7,而没有包含0,是因为功能0并不代表真正的功能,而代表CIA寄存器,即Common I/O Area,这个纪录着SDIO卡的一些基本信息和特性,并且可以改写这些寄存器。其中地址0x1000~0x17fff是SDIO卡的CIS区域,就是基本信息区域,Common Information Structure。初始化的时候读取并配对SDIO设备。

       这些寄存器的详细分区已经其对应的功能,在开发过程中都是需要仔细研读的,这些都在协议的SPEC中都有详细说明,这里就不在啰嗦了。 


  有了先前的介绍,我们这边需要为阅读mmc子系统做一点知识储备,重点储备是关于Linux设备模型,以及中断上下文和进程上下文的一些简单介绍。

Linux设备模型:

        这个walfred曾经做好了一个系列,地址是:Linux设备模型学习笔记阅读

进程上下文和中断上下文:

        处理器总处于以下状态中的一种:

        1、内核态,运行于进程上下文,内核代表进程运行于内核空间;

        2、内核态,运行于中断上下文,内核代表硬件运行于内核空间;

        3、用户态,运行于用户空间。

        用户空间的应用程序,通过系统调用,进入内核空间。这个时候用户空间的进程要传递很多变量、参数的值给内核,内核态运行的时候也要保存用户进程的一些寄存器值、变量等。所谓的“进程上下文”,可以看作是用户进程传递给内核的这些参数以及内核要保存的那一整套的变量和寄存器值和当时的环境等。

        硬件通过触发信号,导致内核调用中断处理程序,进入内核空间。这个过程中,硬件的一些变量和参数也要传递给内核,内核通过这些参数进行中断处理。所谓的“中断上下文”,其实也可以看作就是硬件传递过来的这些参数和内核需要保存的一些其他环境(主要是当前被打断执行的进程环境)。


从这一节起,我们正式进入 mmc子系统学习笔记内容,整理了一份Linux下mmc子系统的结构组织框架,相信带着这个框架来理解学习mmc子系统应该会起到不错的效果。下面是mmc子系统的结构组织:

         在Linux中,系统中mmc子系统本身没有任何README文档,所以此算一个"帮助文档"。其将涉及到3条总线,2块设备,2个驱动。

三条总线

总线名

platform

mmc

sdio

类型

struct bus_type

变量名

platform_bus

mmc_bus_type

sdio_bus_type

         这个我们可以在/sys/bus中查看。

两块设备

设备名

msm_sdcc

mmc_card

类型

struct  platform_device

struct mmc_card

备注

msm_sdcc(soc)

由前者sdcc的驱动,对mmc/sd/sdio的统一抽象

       msm_sdcc(全称应该是sdcardcontroller集成到soc,所以属platform_device,优点具有较好的移植性和安全性),mmc_card(由前者sdcc的驱动,对mmc/sd/sdio的统一抽象);

 两组驱动

驱动名

msmsdcc_driver

mmc_driver

类型

struct platform_driver

struct mmc_driver

备注

 

 

         对应与上面的2块设备,分别是msmsdcc_driver,mmc_driver;

它们之间的关系

         关于platform总线,下文研究的主线一

         驱动msmsdcc_driver,以及设备msm_sdcc是通过platform_bus关联。

         关于mmc总线,下文研究的主线二

         驱动mmc_driver,以及设备mmc_card是通过该总线关联

         关于sdio总线

         尚未过多研究,此处暂时并考虑。以后会陆续补充。

相关结构体

这是描述一个platform设备的

struct platform_device {

     const char    * name;

     int      id;

     struct device dev;

     u32      num_resources;

     struct resource    * resource;

 

     const struct platform_device_id  *id_entry;

 

     /* arch specific additions */

     struct pdev_archdata   archdata;

};

这是msm_device_sdc设备

struct platform_device msm_device_sdc1 = {

     .name         = "msm_sdcc",//platform_device名

     .id      = 1,

     .num_resources     = ARRAY_SIZE(resources_sdc1),//资源个数

     .resource = resources_sdc1,//指向资源的指针

     .dev     = {

         .coherent_dma_mask = 0xffffffff,

     },

};

platform driver这是描述一个msmsdcc_driver设备驱动的

static struct platform_driver msmsdcc_driver = {

     .probe        = msmsdcc_probe,

     .suspend = msmsdcc_suspend,

     .resume       = msmsdcc_resume,

     .driver       = {

         .name    = "msm_sdcc",

     },

};

描述mmc card的结构体

/*

 * MMC device

 */

struct mmc_card {

     struct mmc_host        *host;        /* the host this device belongs to */

     struct device      dev;     /* the device */

     unsigned int       rca;     /* relative card address of device */

     unsigned int       type;         /* card type */

#define MMC_TYPE_MMC        0        /* MMC card */

#define MMC_TYPE_SD         1        /* SD card */

#define MMC_TYPE_SDIO       2        /* SDIO card */

     unsigned int       state;        /* (our) card state */

#define MMC_STATE_PRESENT   (1<<0)        /* present in sysfs */

#define MMC_STATE_READONLY  (1<<1)        /* card is read-only */

#define MMC_STATE_HIGHSPEED (1<<2)        /* card is in high speed mode */

#define MMC_STATE_BLOCKADDR (1<<3)        /* card uses block-addressing */

     unsigned int       quirks; /* card quirks */

#define MMC_QUIRK_LENIENT_FN0    (1<<0)        /* allow SDIO FN0 writes outside of the VS CCCR range */

#define MMC_QUIRK_BLKSZ_FOR_BYTE_MODE (1<<1)   /* use func->cur_blksize */

                            /* for byte mode */

 

     u32           raw_cid[4];   /* raw card CID */

     u32           raw_csd[4];   /* raw card CSD */

     u32           raw_scr[2];   /* raw card SCR */

     struct mmc_cid         cid;     /* card identification */

     struct mmc_csd         csd;     /* card specific */

     struct mmc_ext_csd ext_csd; /* mmc v4 extended card specific */

     struct sd_scr      scr;     /* extra SD information */

     struct sd_switch_caps  sw_caps; /* switch (CMD6) caps */

 

     unsigned int       sdio_funcs;   /* number of SDIO functions */

     struct sdio_cccr   cccr;         /* common card info */

     struct sdio_cis        cis;     /* common tuple info */

     struct sdio_func   *sdio_func[SDIO_MAX_FUNCS]; /* SDIO functions (devices) */

     unsigned      num_info; /* number of info strings */

     const char         **info;       /* info strings */

     struct sdio_func_tuple *tuples; /* unknown common tuples */

 

     struct dentry      *debugfs_root;

};

描述mmc card驱动的结构体

static struct mmc_driver mmc_driver = {

     .drv     = {

         .name    = "mmcblk",

     },

     .probe        = mmc_blk_probe,

     .remove       = mmc_blk_remove,

     .suspend = mmc_blk_suspend,

     .resume       = mmc_blk_resume,

};

控制的结构体

struct msmsdcc_host {

     struct resource        *cmd_irqres;

     struct resource        *pio_irqres;

     struct resource        *memres;

     struct resource        *dmares;

     void __iomem       *base;

     int           pdev_id;

     unsigned int       stat_irq;

 

     struct msmsdcc_curr_req curr;

 

     struct mmc_host        *mmc;

     struct clk         *clk;         /* main MMC bus clock */

     struct clk         *pclk;        /* SDCC peripheral bus clock */

     unsigned int       clks_on; /* set if clocks are enabled */

     struct timer_list  busclk_timer;

 

     unsigned int       eject;        /* eject state */

 

     spinlock_t         lock;

 

     unsigned int       clk_rate; /* Current clock rate */

     unsigned int       pclk_rate;

 

     u32           pwr;

     u32           saved_irq0mask;    /* MMCIMASK0 reg value */

     struct mmc_platform_data *plat;

 

     struct timer_list  timer;

     unsigned int       oldstat;

 

     struct msmsdcc_dma_data dma;

     struct msmsdcc_pio_data pio;

     int           cmdpoll;

     struct msmsdcc_stats   stats;

 

#ifdef CONFIG_MMC_MSM7X00A_RESUME_IN_WQ

     struct work_struct resume_task;

#endif

 

     /* Command parameters */

     unsigned int       cmd_timeout;

     unsigned int       cmd_pio_irqmask;

     unsigned int       cmd_datactrl;

     struct mmc_command *cmd_cmd;

     u32           cmd_c;

 

};



  有了上一篇 mmc子系统学习笔记四 mmc子系统的结构组织的介绍,看下面的代码似乎容易了许多,这篇文章,基于Linux 2.6.32 高通msm的bsp代码,以及mmc子系统的驱动代码先交待出来,这样看起来会更有针对性。

BSP相关代码

         在soc系统中,sdcc不可能单独使用cpu的接口连接mmc总线,或者sdio总线,也不会使用spi,sci等,故直接集成到cpu中,充作一个platform_device。

     arch/arm/

         mach-msm/

              board-mahimahi-mmc.c /*初始化platform_device,并将其添加到platform_bus上*/

              board-qsd8x50.c /*platform_device的资源定义*/

 mmc驱动相关代码

     driver/

         mmc/

              card/

                   block.c /*描述了一个块设备驱动;*/

                   queue.c /*上述块设备驱动的请求队列等的实现;*/

 

              care/  /*mmc子系统的核心层次*/

                   bus.c /*mmc_bus相关,包括驱动mmc_driver注册,及设备mmc_card设备及驱动注册*/

                   core.c /*核心,调用sd,mmc,sdio操作函数*/

                      

                   mmc_ops.c /*cmd0 cmd8*/

                   mmc.c /*主要是将mmc设备初始化成mmc_card添加到mmc总线,在添加之前需要完成的初始化,获取寄存器信息等;*/

                    

                   sdio_ops.c /*cmd5和cmd52*/

                   sdio.c 主要是将sdio设备初始化成mmc_card添加到mmc总线,在添加之前需要完成的初始化,获取寄存器信息等;

 

                   sd_ops.c /*cmd41相关*/

                   sd.c 主要是将sd设备初始化成mmc_card添加到mmc总线,在添加之前需要完成的初始化,获取寄存器信息等;

 

              host/

                   host.c /*和host相关的一些函数*/

                   msm_sdcc.c /*Qualcomm MSM 7X00A SDCC Driver*/

                   其他平台暂时忽略


 在mmc子系统学习笔记四 mmc子系统的结构组织中,谈到了mmc子系统中,我们只要看这两条主线就几乎可以吃透整个mmc子系统了,下面我们先看第一条主线。

主线一,关于platform总线

        设备msm_sdcc,以及驱动msmsdcc_driver是通过platform_bus关联。

        platform总线,暂且理解成系统就已经存在的。

platform_device msm_sdcc

         代码位置:mmc子系统学习笔记五 mmc子系统的代码组织

         系统通过调用mahimahi_init_mmc初始化设备,并通过调用msm_add_sdcc将设备注册进platform bus。

         代码清单:

int __init mahimahi_init_mmc(unsigned int sys_rev, unsigned debug_uart) //系统在初始化完成的,对与参数sys_rev需要考虑其值

{

     uint32_t id;

 

     printk("%s()+\n", __func__);

 

     /* initial WIFI_SHUTDOWN# */

     id = PCOM_GPIO_CFG(MAHIMAHI_GPIO_WIFI_SHUTDOWN_N, 0, GPIO_OUTPUT, GPIO_NO_PULL, GPIO_2MA),

     msm_proc_comm(PCOM_RPC_GPIO_TLMM_CONFIG_EX, &id, 0);

 

     msm_add_sdcc(1, &mahimahi_wifi_data, 0, 0);/*第一次调用,整型量1表示数组下表,

                            mahimahi_wifi_data是除却mem,irq,dma其他的相关资源集合,可以看出这是关于sdio相关的资源*/

 

     if (debug_uart) {

         pr_info("%s: sdcard disabled due to debug uart\n", __func__);

         goto done;

     }

     if (opt_disable_sdcard) {

         pr_info("%s: sdcard disabled on cmdline\n", __func__);

         goto done;

     }

 

     sdslot_vreg_enabled = 0;

 

     if (is_cdma_version(sys_rev)) {

         /* In the CDMA version, sdslot is supplied by a gpio. */

         int rc = gpio_request(MAHIMAHI_CDMA_SD_2V85_EN, "sdslot_en");

         if (rc < 0) {

              pr_err("%s: gpio_request(%d) failed: %d\n", __func__,

                   MAHIMAHI_CDMA_SD_2V85_EN, rc);

              return rc;

         }

         mahimahi_sdslot_data.translate_vdd = mahimahi_cdma_sdslot_switchvdd;

     } else {

         /* in UMTS version, sdslot is supplied by pmic */

         sdslot_vreg = vreg_get(0, "gp6");

         if (IS_ERR(sdslot_vreg))

              return PTR_ERR(sdslot_vreg);

     }

 

     if (system_rev > 0)

         msm_add_sdcc(2, &mahimahi_sdslot_data, 0, 0); /*这边同上,是第二次添加platform device到platform bus上,注意这次是传的2,

                                                                                    加上额外的资源mahimahi_sdslot_data,*/

     else {

         mahimahi_sdslot_data.status = mahimahi_sdslot_status_rev0;

         mahimahi_sdslot_data.register_status_notify = NULL;

         set_irq_wake(MSM_GPIO_TO_INT(MAHIMAHI_GPIO_SDMC_CD_REV0_N), 1);

         msm_add_sdcc(2, &mahimahi_sdslot_data,

                   MSM_GPIO_TO_INT(MAHIMAHI_GPIO_SDMC_CD_REV0_N),

                   IORESOURCE_IRQ_LOWEDGE | IORESOURCE_IRQ_HIGHEDGE);

     }

 

done:

     printk("%s()-\n", __func__);

     return 0;

}

 

int __init msm_add_sdcc(unsigned int controller, struct mmc_platform_data *plat,

              unsigned int stat_irq, unsigned long stat_irq_flags)

{

     struct platform_device *pdev;

     struct resource *res;

 

     if (controller < 1 || controller > 4)

         return -EINVAL;

 

     pdev = msm_sdcc_devices[controller-1];//根据传过来的1和2,可知道pdev将是msm_sdcc_devices[0],msm_sdcc_devices[1]

     pdev->dev.platform_data = plat;

 

     res = platform_get_resource_byname(pdev, IORESOURCE_IRQ, "status_irq");

     if (!res)

         return -EINVAL;

     else if (stat_irq) {

         res->start = res->end = stat_irq;

         res->flags &= ~IORESOURCE_DISABLED;

         res->flags |= stat_irq_flags;

     }

 

     return platform_device_register(pdev);//最后挂到iplatform bus上

}

//msm_sdcc_devices定义了4个元素,可以看下每一个元素

static struct platform_device *msm_sdcc_devices[] __initdata = {

     &msm_device_sdc1,

     &msm_device_sdc2,

     &msm_device_sdc3,

     &msm_device_sdc4,

};

 

struct platform_device msm_device_sdc1 = {

     .name         = "msm_sdcc",//platform_device名

     .id      = 1,

     .num_resources     = ARRAY_SIZE(resources_sdc1),//资源个数

     .resource = resources_sdc1,//指向资源的指针

     .dev     = {

         .coherent_dma_mask = 0xffffffff,

     },

};

 

struct platform_device msm_device_sdc2 = {

     .name         = "msm_sdcc",

     .id      = 2,

     .num_resources     = ARRAY_SIZE(resources_sdc2),

     .resource = resources_sdc2,

     .dev     = {

         .coherent_dma_mask = 0xffffffff,

     },

};

资源可以看下

static struct resource resources_sdc1[] = {

     {

         .start   = MSM_SDC1_PHYS,

         .end = MSM_SDC1_PHYS + MSM_SDC1_SIZE - 1,

         .flags   = IORESOURCE_MEM,

     },

     {

         .start   = INT_SDC1_0,

         .end = INT_SDC1_0,

         .flags   = IORESOURCE_IRQ,

         .name    = "cmd_irq",

     },

     {

         .start   = INT_SDC1_1,

         .end = INT_SDC1_1,

         .flags   = IORESOURCE_IRQ,

         .name    = "pio_irq",

     },

     {

         .flags   = IORESOURCE_IRQ | IORESOURCE_DISABLED,

         .name    = "status_irq"

     },

     {

         .start   = 8,

         .end = 8,

         .flags   = IORESOURCE_DMA,

     },

};

         另:一共定义了四个元素大小的数组msm_sdcc_devices,最终只用了下表为01的两个元素msm_sdcc_devices[0]msm_sdcc_devices[1]。对比这两个,可以看得出他们的名字都是msm_sdcc,只不过他们分配的资源不一样,比如中断号(是共享中断号2627...)和内存地址。当然额外的mahimahi_wifi_datamahimahi_sdslot_data更是不一样的。这样这个msm_sdd就注册到platform bus上了。

platform_driver msmsdcc_driver

         代码位置

         driver/

              host/

                   msm_sdcc.c /*Qualcomm MSM 7X00A SDCC Driver*/

                   其他平台暂时忽略

         代码清单:

         static int __init msmsdcc_init(void)

{

     return platform_driver_register(&msmsdcc_driver);

}

         然后看下,msmsdcc_driver这个结构体

static struct platform_driver msmsdcc_driver = {

     .probe        = msmsdcc_probe,

     .suspend = msmsdcc_suspend,

     .resume       = msmsdcc_resume,

     .driver       = {

         .name    = "msm_sdcc",

     },

};

看看这个msmsdcc_driver

         主要完成上述结构体的任务,这里主要分析msmsdcc_probe函数,这个很重要,他的任务是巨大的,下面小节会很详细的分析,这个知道大概先,需要完成:

        (1)构造msmsdcc_host这个结构体,并和mmc_host这个结构体关联起来,mmc_host是主host(真正的)是和device通信的,而msmsdcc_host只不过是结合msm的平台虚构出来的,他们关联之后,mmc_host就能找到msmsdcc_host了。这样msmsdcc_sdcc就可以和接到这个平台的mmc系列卡通信了。

        (2)定时/延时函数的

        (3)读写中断处理程序的注册

        (4)抽象出mmc_card结构的设备

下面是代码的分析:

static int msmsdcc_probe(struct platform_device *pdev)

{

     struct mmc_platform_data *plat = pdev->dev.platform_data;

     struct msmsdcc_host *host;

     struct mmc_host *mmc;

     struct resource *cmd_irqres = NULL;

     struct resource *pio_irqres = NULL;

     struct resource *stat_irqres = NULL;

     struct resource *memres = NULL;

     struct resource *dmares = NULL;

     int ret;

 

     /* must have platform data */

     if (!plat) {

         pr_err("%s: Platform data not available\n", __func__);

         ret = -EINVAL;

         goto out;

     }

 

     if (pdev->id < 1 || pdev->id > 4)

         return -EINVAL;

 

     if (pdev->resource == NULL || pdev->num_resources < 2) {

         pr_err("%s: Invalid resource\n", __func__);

         return -ENXIO;

     }

 

     memres = platform_get_resource(pdev, IORESOURCE_MEM, 0);

     dmares = platform_get_resource(pdev, IORESOURCE_DMA, 0);

     cmd_irqres = platform_get_resource_byname(pdev, IORESOURCE_IRQ,

                              "cmd_irq");

     pio_irqres = platform_get_resource_byname(pdev, IORESOURCE_IRQ,

                              "pio_irq");

     stat_irqres = platform_get_resource_byname(pdev, IORESOURCE_IRQ,

                               "status_irq");

 

     if (!cmd_irqres || !pio_irqres || !memres) {

         pr_err("%s: Invalid resource\n", __func__);

         return -ENXIO;

     }

 

     /*

      * Setup our host structure

      */

 

     mmc = mmc_alloc_host(sizeof(struct msmsdcc_host), &pdev->dev);

     if (!mmc) {

         ret = -ENOMEM;

         goto out;

     }/*完成上述任务一,具体的可以进去看看,里面定义了一个延时工作*/

 

     host = mmc_priv(mmc);/*关联*/

     host->pdev_id = pdev->id;

     host->plat = plat;

     host->mmc = mmc;

     host->curr.cmd = NULL;

 

     host->cmdpoll = 1;

 

     host->base = ioremap(memres->start, PAGE_SIZE);

     if (!host->base) {

         ret = -ENOMEM;

         goto out;

     }

 

     host->cmd_irqres = cmd_irqres;

     host->pio_irqres = pio_irqres;

     host->memres = memres;

     host->dmares = dmares;

     spin_lock_init(&host->lock);

 

#ifdef CONFIG_MMC_EMBEDDED_SDIO

     if (plat->embedded_sdio)

         mmc_set_embedded_sdio_data(mmc,

                          &plat->embedded_sdio->cis,

                          &plat->embedded_sdio->cccr,

                          plat->embedded_sdio->funcs,

                          plat->embedded_sdio->num_funcs);

#endif

 

     /*

      * Setup DMA

      */

     msmsdcc_init_dma(host);

 

     /* Get our clocks */

     host->pclk = clk_get(&pdev->dev, "sdc_pclk");

     if (IS_ERR(host->pclk)) {

         ret = PTR_ERR(host->pclk);

         goto host_free;

     }

 

     host->clk = clk_get(&pdev->dev, "sdc_clk");

     if (IS_ERR(host->clk)) {

         ret = PTR_ERR(host->clk);

         goto pclk_put;

     }

 

     /* Enable clocks */

     ret = msmsdcc_enable_clocks(host);

     if (ret)

         goto clk_put;

 

     ret = clk_set_rate(host->clk, msmsdcc_fmin);

     if (ret) {

         pr_err("%s: Clock rate set failed (%d)\n", __func__, ret);

         goto clk_disable;

     }

 

     host->pclk_rate = clk_get_rate(host->pclk);

     host->clk_rate = clk_get_rate(host->clk);

 

     /*

      * Setup MMC host structure

      */

     mmc->ops = &msmsdcc_ops; /*操作函数,由最上层的card调用的操作*/

     mmc->f_min = msmsdcc_fmin;

     mmc->f_max = msmsdcc_fmax;

     mmc->ocr_avail = plat->ocr_mask;

 

     if (msmsdcc_4bit)

         mmc->caps |= MMC_CAP_4_BIT_DATA;

     if (msmsdcc_sdioirq)

         mmc->caps |= MMC_CAP_SDIO_IRQ;

     mmc->caps |= MMC_CAP_MMC_HIGHSPEED | MMC_CAP_SD_HIGHSPEED;

 

     mmc->max_phys_segs = NR_SG;

     mmc->max_hw_segs = NR_SG;

     mmc->max_blk_size = 4096;   /* MCI_DATA_CTL BLOCKSIZE up to 4096 */

     mmc->max_blk_count = 65536;

 

     mmc->max_req_size = 33554432;    /* MCI_DATA_LENGTH is 25 bits */

     mmc->max_seg_size = mmc->max_req_size;

 

     msmsdcc_writel(host, 0, MMCIMASK0);

     msmsdcc_writel(host, 0x5e007ff, MMCICLEAR);

 

     msmsdcc_writel(host, MCI_IRQENABLE, MMCIMASK0);

     host->saved_irq0mask = MCI_IRQENABLE;

 

     mmc->pm_caps = MMC_PM_KEEP_POWER | MMC_PM_IGNORE_PM_NOTIFY;

     if (plat->built_in)

         mmc->pm_flags = MMC_PM_KEEP_POWER | MMC_PM_IGNORE_PM_NOTIFY;

 

     /*

      * Setup card detect change

      */

 

     memset(&host->timer, 0, sizeof(host->timer));

 

     if (stat_irqres && !(stat_irqres->flags & IORESOURCE_DISABLED)) {

         unsigned long irqflags = IRQF_SHARED |

              (stat_irqres->flags & IRQF_TRIGGER_MASK);

 

         host->stat_irq = stat_irqres->start;

         ret = request_irq(host->stat_irq,

                     msmsdcc_platform_status_irq,

                     irqflags,

                     DRIVER_NAME " (slot)",

                     host);/**中断处理程序的注册一,卡状态*/

         if (ret) {

              pr_err("%s: Unable to get slot IRQ %d (%d)\n",

                     mmc_hostname(mmc), host->stat_irq, ret);

              goto clk_disable;

         }

     } else if (plat->register_status_notify) {

         plat->register_status_notify(msmsdcc_status_notify_cb, host);

     } else if (!plat->status)

         pr_err("%s: No card detect facilities available\n",

                mmc_hostname(mmc));

     else {

         init_timer(&host->timer);

         host->timer.data = (unsigned long)host;

         host->timer.function = msmsdcc_check_status;

         host->timer.expires = jiffies + HZ;

         add_timer(&host->timer);

     }/*定时器相关*/

 

     if (plat->status) {

         host->oldstat = host->plat->status(mmc_dev(host->mmc));

         host->eject = !host->oldstat;

     }

 

     init_timer(&host->busclk_timer);

     host->busclk_timer.data = (unsigned long) host;

     host->busclk_timer.function = msmsdcc_busclk_expired;

 

     ret = request_irq(cmd_irqres->start, msmsdcc_irq, IRQF_SHARED,

                DRIVER_NAME " (cmd)", host);/*中断处理程序的注册二,msmsdcc_irq为中断处理函数,下面会分析的,命令状态*/

     if (ret)

         goto stat_irq_free;

 

     ret = request_irq(pio_irqres->start, msmsdcc_pio_irq, IRQF_SHARED,

                DRIVER_NAME " (pio)", host);/*中断处理程序的注册三,msmsdcc_pio_irq为中断处理函数,下面会分析的,读写卡*/

     if (ret)

         goto cmd_irq_free;

 

     mmc_set_drvdata(pdev, mmc);

     mmc_add_host(mmc);/*完成任务四,这个在下文还需要具体分析*/

 

     pr_info("%s: Qualcomm MSM SDCC at 0x%016llx irq %d,%d dma %d\n",

         mmc_hostname(mmc), (unsigned long long)memres->start,

         (unsigned int) cmd_irqres->start,

         (unsigned int) host->stat_irq, host->dma.channel);

     pr_info("%s: 4 bit data mode %s\n", mmc_hostname(mmc),

         (mmc->caps & MMC_CAP_4_BIT_DATA ? "enabled" : "disabled"));

     pr_info("%s: MMC clock %u -> %u Hz, PCLK %u Hz\n",

         mmc_hostname(mmc), msmsdcc_fmin, msmsdcc_fmax, host->pclk_rate);

     pr_info("%s: Slot eject status = %d\n", mmc_hostname(mmc), host->eject);

     pr_info("%s: Power save feature enable = %d\n",

         mmc_hostname(mmc), msmsdcc_pwrsave);

 

     if (host->dma.channel != -1) {

         pr_info("%s: DM non-cached buffer at %p, dma_addr 0x%.8x\n",

              mmc_hostname(mmc), host->dma.nc, host->dma.nc_busaddr);

         pr_info("%s: DM cmd busaddr 0x%.8x, cmdptr busaddr 0x%.8x\n",

              mmc_hostname(mmc), host->dma.cmd_busaddr,

              host->dma.cmdptr_busaddr);

     } else

         pr_info("%s: PIO transfer enabled\n", mmc_hostname(mmc));

     if (host->timer.function)

         pr_info("%s: Polling status mode enabled\n", mmc_hostname(mmc));

 

#if BUSCLK_PWRSAVE

     msmsdcc_disable_clocks(host, 1);

#endif

     return 0;

 cmd_irq_free:

     free_irq(cmd_irqres->start, host);

 stat_irq_free:

     if (host->stat_irq)

         free_irq(host->stat_irq, host);

 clk_disable:

     msmsdcc_disable_clocks(host, 0);

 clk_put:

     clk_put(host->clk);

 pclk_put:

     clk_put(host->pclk);

 host_free:

     mmc_free_host(mmc);

 out:

     return ret;

}


看完,那我们就着看主线二,再提醒下mmc子系统学习笔记框架在mmc子系统学习笔记一 序

关于mmc总线,这是研究的主线二

         驱动mmc_driver,以及设备mmc_card是通过该总线关联。

         我们知道上面的mmc子系统学习笔记六 基于platform总线的驱动代码研究已经生产出来了mmc_card,这个mmc_card添加到哪儿呢?

         答案是添加到mmc总线上了,那么mmc总线肯定是在mmc_card生产出来之前就已经向系统注册了吗?因为关于mmc总线的我们的device已经有了。下面不说了。

mmc_bus相关代码

         这个是上述驱动代码总最先注册的。看下代码:

         位置:

              care/  /*mmc子系统的核心层次*/

                   bus.c /*mmc_bus相关,包括驱动mmc_driver注册,及设备mmc_card设备及驱动注册*/

                   core.c /*核心,调用sd,mmc,sdio操作函数*/

         这是在core.c调用bus.c和sdio_bus.c中的mmc 和 sdio总线注册函数   

static int __init mmc_init(void)

{

     int ret;

 

     wake_lock_init(&mmc_delayed_work_wake_lock, WAKE_LOCK_SUSPEND, "mmc_delayed_work");

 

     workqueue = create_singlethread_workqueue("kmmcd");

     if (!workqueue)

         return -ENOMEM;

 

     ret = mmc_register_bus();

     if (ret)

         goto destroy_workqueue;

 

     ret = mmc_register_host_class();

     if (ret)

         goto unregister_bus;

 

     ret = sdio_register_bus();

     if (ret)

         goto unregister_host_class;

 

     return 0;

 

unregister_host_class:

     mmc_unregister_host_class();

unregister_bus:

     mmc_unregister_bus();

destroy_workqueue:

     destroy_workqueue(workqueue);

     return ret;

}

       而调用上述函数的是subsys_initcall(mmc_init);什么是subsys_initcall()呢?系统初始化某些子系统的时候的入口函数,所以在系统起来的时候就已经地用了。

关于mmc_card

       上面5.1.4已经生产出来了mmc card这边bu就赘述了。

关于mmc_driver

         mmc driver是mmc card的驱动,这个驱动和一般的块设备驱动没什么两样。

         代码位置:

         driver/

         mmc/

              card/

                   block.c /*描述了一个块设备驱动;*/

                   queue.c /*上述块设备驱动的请求队列等的实现;*/

这边主要分析块设备层中的block.c  和queue.c

module_init(mmc_blk_init);

mmc_blk_init(void);

        (1)register_blkdev 注册mmc驱动

        (2)mmc_register_driver(&mmc_driver) 向总线注册为mmc_bus_driver;

        调用 core/bus.c brv->drv.bus = &mmc_bus_type;

                        driver/base/driver.c

        注册设备驱动程序,搜寻drv对应的设备并关联;

详述下struct mmc_driver mmc_driver

        .probe          = mmc_blk_probe,

        .remove         = mmc_blk_remove,

        .suspend        = mmc_blk_suspend,

        .resume         = mmc_blk_resume,

static int mmc_blk_probe(struct mmc_card *card);这个函数主要完成的任务

A,检查命令集

B,mmc_blk_alloc  分配card

         a,find_first_zero_bit 寻找第一个值为0的区域

         b,kzalloc 分配数据包

         c,判读卡是否为只读卡 mmc_blk_readonly

         d,分配gendisk

         e,自旋锁初始化spin_lock_init(&md->lock)

         f,mmc_init_queue 请求队列初始化    下文中重点介绍

         g,md->queue.issue_fn = mmc_blk_issue_rq下文中重点介绍

         h,初始化md->disk结构体 其中fops = &mmc_bops

              其中f:

              是在queue.c中实现的

int mmc_init_queue(struct mmc_queue *mq, struct mmc_card *card, spinlock_t *lock);

完成:

         1.mq->queue = blk_init_queue(mmc_request, lock);初始化将request函数与队列绑定

         2.blk_queue_prep_rq(mq->queue, mmc_prep_request);

        命令预处理,为驱动程序在返回evl_next_request之前,提供检查和预处理请求的机制

         3.blk_queue_ordered(mq->queue, QUEUE_ORDERED_DRAIN, NULL);/barrier request屏障请求,防止重新组合产生的错误,设置标准后,保证请求的数据及时写入到介质。

         4.mq->thread = kthread_run(mmc_queue_thread, mq, "mmcqd");最后设置了一个内核线程,线程关联的函数是mmc_queue_thread,这个很重要,待会分析。

C,接下来调用 mmc_blk_set_blksize来设置block的长度为512。

D,一切都准备好了以后激活磁 盘:add_disk(md->disk);

IO请求

mq->queue = blk_init_queue(mmc_request, lock)

详细分析

1.req = blk_fetch_request(q)

2.wake_up_process(mq->thread);

通过什么来进行数据块的传输呢这里就是通过唤醒

mq->thread线程来实现的这个线程实际上就是mmc_queue_thread函数

         a,获取请求

         b,mq->issue_fn(mq, req);

看看

         mq->issue_fn(mq, req);干了什么

          md->queue.issue_fn = mmc_blk_issue_rq;

       还回到block.c 

static int mmc_blk_issue_rq(struct mmc_queue *mq, struct request *req);

scatterlist 数据缓冲区

mmc_wait_for_req(card->host, &mrq);

  位于core/core.c

/**

 *      mmc_wait_for_req - start a request and wait for completion

 *      @host: MMC host to start command

 *      @mrq: MMC request to start

 *

 *      Start a new MMC custom command request for a host, and wait

 *      for the command to complete. Does not attempt to parse the

 *      response.

 */

void mmc_wait_for_req(struct mmc_host *host, struct mmc_request *mrq)

{

        DECLARE_COMPLETION_ONSTACK(complete);

        mrq->done_data = &complete;

mrq->done = mmc_wait_done;

mmc_start_request(host, mrq);

        wait_for_completion(&complete);

}

作用就是:向host发送请求,mmc_start_request(host, mrq);的最后一句就是 host->ops->request(host, mrq),这样就和我们msmsdcc的驱动程序的定义的操作集合中的request联系起来了,由于这次cmd—>data成员不再为空,所以启动的是数据传输。


mmc子系统学习笔记七 基于mmc总线的驱动代码研究中我们看到了最后添加mmc_card设备到mmc总线上,那么在添加之前总线控制器发现了有卡插进了卡槽,做了什么操作,构造mmc card这个设备呢?

       还回到host下的msmsdcc-host.c文件中。

       当然还是msmsdcc_probe函数,在上面5.2中我已经简要的分析了,但之前分析的时候,故意把一些重要的问题留到后面,也就是这儿。

       在这个函数的最后,调用了mmc_add_host(mmc);

       那这个mmc_add_host(mmc);干了什么么?

       发现了mmc_start_host(host)函数,然后又调用了mmc_detect_change(host, 0);

void mmc_detect_change(struct mmc_host *host, unsigned long delay)

{

#ifdef CONFIG_MMC_DEBUG

     unsigned long flags;

     spin_lock_irqsave(&host->lock, flags);

     WARN_ON(host->removed);

     spin_unlock_irqrestore(&host->lock, flags);

#endif

 

     mmc_schedule_delayed_work(&host->detect, delay);

}

mmc_schedule_delayed_work(&host->detect, delay);/*延时函数,在probe最后的定时器负责polling,一旦host寄存器发生变化就调用延时的工作函数mmc_rescan,那现在假设polling的时候发现了host那块的寄存器状态改变,延时函数mmc_rescan做了什么?*/

初始化插入的设备

        无论什么卡设备统一发送cmd0和cmd8

        在mmc_go_idle(host);发送cmd0

        cmd 0复位。

        在mmc_send_if_cond(host, host->ocr_avail);发送cmd8

        cmd 8 是否是sd 2.0即高容量

首先判断是不是sdio设备

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

     if (!err) {

         if (mmc_attach_sdio(host, ocr))

              mmc_power_off(host);

         extend_wakelock = 1;

         goto out;

     }

        mmc_send_io_op_cond(),这个函数是cmd5,ocr为0,即arg=0,回应是r4,正确的response则继续mmc_attach_sdio(host, ocr))需要完成以下工作:

        (1)host->ocr = mmc_select_voltage(host, ocr);选择ocr中主机支持,且为最低的电压

        (2)告诉卡,主机的的工作电压,这一步仍发送cmd5,但是arg有参数了,即是主机的选中的工作电压

        (3)分配mmc card,并开始初始化

        (4)发布rca,cmd3命令

        (5)读取cia中通用寄存器cccr,使用地址0x00来完成操作function0,注CIA包含 CCCR+FBR+CIS

        (6)最后err = mmc_add_card(host->card);

是不是sd设备

        和上述差不多,可自行分析,注意获取ocr的命令此时变成acmd41.


  通过上面一系列代码的分析,我们已经晓得了bsp资源那边定义了3个中断,分别状态status_irq,命令cmd_irq,数据pio_irq。

         那么我们看下这个中断上个怎么工作的?

         中断处理这边就不再赘述了。稍微提下的是,mmc这块中断处理的下半部采用的是工作队列(workqueue)。

mmc中断上半部

         注册中断处理程序

     代码:仍然在msmsdcc_probe函数中

ret = request_irq(host->stat_irq,

                     msmsdcc_platform_status_irq,

                     irqflags,

                     DRIVER_NAME " (slot)",

                     host);

         参数分别是:irq号,中断处理程序(上半部),中断标识号这个是在前面定义过了IRQF_SHARED即共享中断号,后面两个参数是因为是共享中断而指定的该中断相关标记

         看看实际的中断处理程序(ISR)msmsdcc_platform_status_irq()

static irqreturn_t

msmsdcc_platform_status_irq(int irq, void *dev_id)

{

     struct msmsdcc_host *host = dev_id;

 

     printk(KERN_DEBUG "%s: %d\n", __func__, irq);

     msmsdcc_check_status((unsigned long) host);

     return IRQ_HANDLED;

}主要是完成msmsdcc_check_status,然后在这个函数中调用mmc_detect_change

void mmc_detect_change(struct mmc_host *host, unsigned long delay)

{

#ifdef CONFIG_MMC_DEBUG

     unsigned long flags;

     spin_lock_irqsave(&host->lock, flags);

     WARN_ON(host->removed);

     spin_unlock_irqrestore(&host->lock, flags);

#endif

 

     mmc_schedule_delayed_work(&host->detect, delay);

}最后这个是下半部的处理函数了

mmc中断下半部

         工作者线程是在自己定义,未用默认的envents,而是自己定义的工作者线程,在core.c文件中,定义的:

        static struct workqueue_struct *workqueue; 定义工作者线程

        workqueue = create_singlethread_workqueue("kmmcd");创建所有的工作者线程,每一个cpu一个

        对应于7.1的状态中断的请求的上半部,其下半部任务为

        mmc_schedule_delayed_work(&host->detect, delay);

        等到延时delay个节拍就执行mmc_rescan,关于detect是怎么和mmc_rescan请看mmc = mmc_alloc_host(sizeof(struct msmsdcc_host), &pdev->dev);函数

关于另两个irq

       cmd_irq,数据pio_irq,请自行分析。

十一

根据 mmc子系统学习笔记九 mmc子系统中相关中断,晓得了msmsdcc_check_status是status_irq中断处理程序,但是如果这个中断不可以,種么办?

if (stat_irqres && !(stat_irqres->flags & IORESOURCE_DISABLED)) {

     .....

     else {

         init_timer(&host->timer); //初始化定时器

         host->timer.data = (unsigned long)host; //超时处理函数的参数

         host->timer.function = msmsdcc_check_status;//超时处理函数

         host->timer.expires = jiffies + HZ;//定义超时

         add_timer(&host->timer);//激活该定时器

     }

     这边定义了一个定时器,名字为timer;

     系统现在的时间为jiffies,当到了这个时间jiffies + HZ(即刚刚的时间加1s,超时1s)就执行msmsdcc_check_status((unsigned long)host);

     这个过程是和笔记九之后是一样的。





  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值