編寫自己的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

}               

}

 

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