编写自己的SD/MMC Host驱动(二):工作过程和大结局

上次说到,如果注册成功的话,Linux就会开始初始化SD/MMC了,SD/MMC的初始化都是通过drivers/mmc/core/core.c里面mmc_rescan来完成的:

void mmc_rescan(struct work_struct *work)

{

        .......

       if (host->ops->get_cd && host->ops->get_cd(host) == 0)

              goto out;

    ...................

       mmc_power_up(host);

       mmc_go_idle(host);

     ...................

    mmc_send_if_cond(host, host->ocr_avail);

       /*

        * First we search for SDIO...

        */

       ...................

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

        ...................

       /*

        * ...then normal SD...

        */

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

        ...................

       /*

        * ...and finally MMC.

        */

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

        ...................

}

首先调用get_cd方法,看看是不是有卡在里面,不然岂不是白忙活!

然后调用mmc_power_up来上电,该函数将调用驱动的set_ios操作函数,控制电源和时钟等。

电也上了,此时就需要调用mmc_go_idleSD/MMC卡进入IDLE状态,如果你对协议比较熟的话,就是发送CMD0了。然后调用mmc_send_if_cond发送CMD8设置接口,这个命令只有SD2.0才会响应,对于1.0标准是没关系的。然后就开始判断插入的卡到底是SDIO,SD还是MMC,其实都是通过发送特定的命令,看看其是否响应来判断的,其实这种方法不是很好,估计协议的设计者开始的时候没有想到会有如此多类型,因此没有设计一条专门的命令来查询卡的类型。可以看到SDIO是通过CMD5来识别的,如果有回应则是SDIO卡。SD卡是通过ACMD41来实现的,由于ACMD需要先发切换命令CMD55,因此如果在request里面打log,将会发现发了两条opcode分别为5541的命令。MMC的判断则是通过CMD1,即MMCsend operation condition 命令。这些命令都是各个类型所独有的,因此可以用来判断卡的类型。

卡的类型确定以后就会调用mmc_init_card来初始化卡,关于发送命令的过程需要参考协议的具体描述。

说了半天,终于应该说SD/MMC驱动中最重要的函数request了,其实SD/MMC的操作还是很单纯的,典型的一问一答型:先发送request,然后等待request完成,如果request没有完成,则不会发起下一个request,如果程序有bug,超过120s都不回应,恭喜你,系统会崩溃的。发送request当然是通过之前注册的mmc_host_ops里面的函数来发起的了,回应则是通过调用mmc_request_done来完成的。Request函数的实现一般是这样的:

static void cbpmci_send_request(struct mmc_host *mmc)

{

       struct mmc_command *cmd = host->cmd_is_stop ? mrq->stop : mrq->cmd;

       if (cmd->data) {

      cbpmci_setup_data(host, mrq->data);

              host->dcnt++;

              cbpmci_prepare_pio(host, cmd->data);

       }

     cbpmci_send_command(host, cmd);

}

首先通过cmd->data是否为空来判断这个命令是否有数据,然后做一些准备工作,比如block size block count之类的东东,这些参数都在cmd->data里面。其实在这一段是不收发实际的数据的,只是准备而已,因为协议规定了,数据必须在命令发完了以后才能收发数据,因此其中一些准备工作也可以在命令完成中断里面来完成。不过标识request究竟对数据是进行读还是写的动作需要在这里完成,因为命令完成中断发生时需要利用这个信息。Samsung的那个prepare写的非常让人困惑,PXA的写得就清楚多了。

static int cbpmci_prepare_pio(struct cbpmci_host *host, struct mmc_data *data)

{

   host->pio_active=XFER_NONE;

   if(data->flags & MMC_DATA_READ)       host->pio_active=XFER_READ;

   if(data->flags & MMC_DATA_WRITE)      host->pio_active=XFER_WRITE;

  

}

紧接着就是调用cbpmci_send_command发送命令了。

命令发送完成后会产生命令完成中断,在处理该中断时应该判断当前的状态,即host->pio_active的值,如果该命令没有数据,则调用mmc_request_done直接返回,如果有则打开读写相关的中断,读写数据,在数据读写完成后在调用mmc_request_done通知上层。一般的读写过程是这样的,以读为例,在收到命令完成的中断后,判断此时处于读状态,然后打开发送FIFOalmost fullfull以及数据完成中断,在这almost fullfull中断的处理过程中读取FIFO的值,放入上层提供的scatterlist中,SD/MMC中的scatterlist操作在上上一篇博文中有详细的描写,当数据完成中断产生或者scatterlist已经填满时调用mmc_request_done通知上层。

在发送命令和数据的过程中都可能产生错误,这些错误都可以通过host->mrq->cmd以及host->mrq->data中的相关元素返回:

       if (stat & HWD_MMC_CMD_RSPTOUT_ERR) cmd->error = -ETIMEDOUT;

       else if (stat & HWD_MMC_CMD_RSP_CRC_ERR&& cmd->flags & MMC_RSP_CRC)             cmd->error = -EILSEQ;

或者:

       if (stat & HWD_MMC_DAT_TOUT_ERR)         data->error = -ETIMEDOUT;

       else if (stat & (HWD_MMC_RXDAT_CRC_ERR|HWD_MMC_CRC_STS_ERR))           data->error = -EILSEQ;

至于发送命令的时候产生的response,则是根据返回是48bit或者136bit有所不同,host->mrq->cmd中的response只定义了4word,也就是128位。在48bit返回值时cmd->resp[0]中是32bitcard statuscmd->resp[1]的高7位是回应的CRC值,第24位是停止位,就是“1”,没有研究代码,不知CRC和停止位不知是否有用。当为136bit时,其中的resp[0]resp[3]中是128bitcard status PXACBP的状态位都需要调整后才能写入resp[0]resp[3],Samsungs3cmci则可以直接从寄存器读出然后赋值,估计这也是三星的处理器之所以火的细节之一。

关于卡的热插拔,当卡插拔的时候,一般会产生中断,在中断中调用mmc_detect_change通知上层

 if(isr_status & HWD_MMC_CARD_STS)mmc_detect_change(host->mmc, msecs_to_jiffies(500));

其实是启动了一个工作队列,看代码可知,该工作队列就是调用mmc_rescan函数,轰轰烈烈一番后,重新回到了原点,世界清静了。

如果各个过程都没有错误的话,在/dev下面就会出现两个设备节点mmcblk0mmcblk0p1,如果没有的话,请问是不是忘敲了mdev –s 了?

此时就可以mount了,不过mount之前由于一般SD/MMC卡都是FAT文件系统,因此还要子在内核中加入dos 文件系统和FAT的支持。然后就可以mount

Mount /dev/mmcblk0p1 /mnt

就可以到/mnt目录下看看你的成果了,如果写人小的文件,比如创建目录,一般要在syncumount的时候才会产生实际的写入动作。

mount的过程中如果出现错误:FAT: codepage cp437 not found 那是你的内核字符集不支持437,在内核中选上就OK了。

以下是各个配置的图片:

1)MMC配置

2)FAT配置

3)Code Page 437配置

4)附录: set_ios范例

set_ios设置一些控制参数,比如时钟频率,因为在初始化正常操作的时候时钟是不一样的,初始化的时候一般小于400K,正常操作的时候是26M52M等等高得多的频率。还有就是控制电源以及SD卡的总线宽度,CBPSD总线宽度1,4,8都是支持的。Set_ios的代码基于文章后面的附录中。

 

static void cbpmci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)

{

    ..........................

    if (ios->power_mode==MMC_POWER_UP) {

   ...................................................

   }

   else if(ios->power_mode=MMC_POWER_ON)  {

     ............................

    }

   else if(ios->power_mode=MMC_POWER_OFF)  {

     ............................

    }

    if (ios->clock)

    {

     /*此处的频率是以HZ为单位的*/

    }

    else

    {

    /*关闭时钟*/

    }

    if (ios->bus_width == MMC_BUS_WIDTH_4)

    {

      //4bit

    }

    else if (ios->bus_width == MMC_BUS_WIDTH_8)

    {

       //8bit

     }

     else

{

  //1bit

}               

}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章