上次說到,如果註冊成功的話,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_idle讓SD/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分別爲55和41的命令。MMC的判斷則是通過CMD1,即MMC的send 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通知上層。一般的讀寫過程是這樣的,以讀爲例,在收到命令完成的中斷後,判斷此時處於讀狀態,然後打開發送FIFO的almost full、full以及數據完成中斷,在這almost full和full中斷的處理過程中讀取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只定義了4個word,也就是128位。在48bit返回值時cmd->resp[0]中是32bit的card status,cmd->resp[1]的高7位是迴應的CRC值,第24位是停止位,就是“1”,沒有研究代碼,不知CRC和停止位不知是否有用。當爲136bit時,其中的resp[0]到resp[3]中是128bit的card status。 PXA和CBP的狀態位都需要調整後才能寫入resp[0]到resp[3],而Samsung的s3cmci則可以直接從寄存器讀出然後賦值,估計這也是三星的處理器之所以火的細節之一。
關於卡的熱插拔,當卡插拔的時候,一般會產生中斷,在中斷中調用mmc_detect_change通知上層
if(isr_status & HWD_MMC_CARD_STS)mmc_detect_change(host->mmc, msecs_to_jiffies(500));
其實是啓動了一個工作隊列,看代碼可知,該工作隊列就是調用mmc_rescan函數,轟轟烈烈一番後,重新回到了原點,世界清靜了。
如果各個過程都沒有錯誤的話,在/dev下面就會出現兩個設備節點mmcblk0和mmcblk0p1,如果沒有的話,請問是不是忘敲了mdev –s 了?
此時就可以mount了,不過mount之前由於一般SD/MMC卡都是FAT文件系統,因此還要子在內核中加入dos 文件系統和FAT的支持。然後就可以mount了
Mount /dev/mmcblk0p1 /mnt
就可以到/mnt目錄下看看你的成果了,如果寫人小的文件,比如創建目錄,一般要在sync或umount的時候纔會產生實際的寫入動作。
在mount的過程中如果出現錯誤:FAT: codepage cp437 not found。 那是你的內核字符集不支持437,在內核中選上就OK了。
以下是各個配置的圖片:
1)MMC配置
2)FAT配置
3)Code Page 437配置
4)附錄: set_ios範例
set_ios設置一些控制參數,比如時鐘頻率,因爲在初始化正常操作的時候時鐘是不一樣的,初始化的時候一般小於400K,正常操作的時候是26M,52M等等高得多的頻率。還有就是控制電源以及SD卡的總線寬度,CBP的SD總線寬度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
}
}