Linux 2.6.38
S3C2440
在“SDIO驅動(9)Host驅動probe實現”中簡單介紹了host操作card的接口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,
};
get_cd,檢測card是否存在,返回值有4種:card存在返回1,不存在返回0;如果不支持這個操作返回-ENOSYS(Function not implemented),支持但出現錯誤返回負的錯誤碼。s3cmci_card_present實現:
static int s3cmci_card_present(struct mmc_host *mmc)
{
struct s3cmci_host *host = mmc_priv(mmc);
struct s3c24xx_mci_pdata *pdata = host->pdata;
int ret;
if (pdata->no_detect)
return -ENOSYS;
ret = gpio_get_value(pdata->gpio_detect) ? 0 : 1;
return ret ^ pdata->detect_invert;
}
3行的小伎倆無需贅言。在硬件連接上,gpio_detect對應的pin用於card detection,如果插入了card那麼該pin的電平爲0;detect_invert作爲一個flag,如果該標誌設置的話carddetection對應的電平反轉,即1電平表示card已經插入。有了以上信息代碼的意圖就昭然明揭了。
get_ro,獲取card的讀寫類型,返回值有4種:0表明這是一張read/write,1說這是一張read-only卡);後兩種返回類型同get_cd函數,s3cmci_get_ro實現:
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;
}
no_wprotect作爲flag標識是否具有讀寫控制功能,如果沒有該功能這直接就是一個r/w卡,於是返回0就OK;否則,gpio_wprotect對應的pin用於card的讀寫狀態信息指示,通過讀取該pin電平判斷,0電平表示該卡read/write。set_ios,針對具體的host controller設置bus的一些特定參數,s3cmci_set_ios實現:
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:
s3c2410_gpio_cfgpin(S3C2410_GPE(5), S3C2410_GPE5_SDCLK);
s3c2410_gpio_cfgpin(S3C2410_GPE(6), S3C2410_GPE6_SDCMD);
s3c2410_gpio_cfgpin(S3C2410_GPE(7), S3C2410_GPE7_SDDAT0);
s3c2410_gpio_cfgpin(S3C2410_GPE(8), S3C2410_GPE8_SDDAT1);
s3c2410_gpio_cfgpin(S3C2410_GPE(9), S3C2410_GPE9_SDDAT2);
s3c2410_gpio_cfgpin(S3C2410_GPE(10), S3C2410_GPE10_SDDAT3);
if (host->pdata->set_power)
host->pdata->set_power(ios->power_mode, ios->vdd);
break;
case MMC_POWER_OFF:
default:
gpio_direction_output(S3C2410_GPE(5), 0);
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行,操作硬件的常識:如果需要修改寄存器的某一(些)位,我們讀取整個寄存器的值,修改指定位後再把整個數據寫入寄存器,這個流程涉及最多的就與和或的使用。這裏就是讀取host controller的控制寄存器(SDICON)。10~34行,根據傳進來的參數執行不同的動作:若是打算使用card就必須配置好各個pin對應的功能,要是該host支持自己的上電邏輯(set_power)就調用之;若是放棄card的使用,這裏就把時鐘信號斷掉(clock引腳設爲輸入功能)、復位整個mmc模塊(mci_con |= S3C2440_SDICON_SDRESET)、設置電源狀態。
36行,通過配置波特率預分頻寄存器(SDIPRE)的值來控制輸出時鐘的頻率,查看datasheet的MMC/SD框圖:
然後參照SDIPRE寄存器的說明s3cmci_set_clk函數就十分明瞭了。
38~44行,使能(1)/禁止(0)時鐘輸出。
54行配置host的數據位寬。
enable_sdio_irq: 配置host是否接收來自sdio的中斷。
根據SDIO Spec,DAT[1]信號線複用爲中斷線,在1BIT模式下DAT[0]用來傳輸數據,DAT[1]用作中斷線;在4BIT模式下DAT0-DAT3用來傳輸數據,其中DAT[1]複用作中斷線,由於複用關係,操作時序及其重要,時序要求參見datasheet。s3cmci_enable_sdio_irq的實現:
static void s3cmci_enable_sdio_irq(struct mmc_host *mmc, int enable)
{
struct s3cmci_host *host = mmc_priv(mmc);
unsigned long flags;
u32 con;
local_irq_save(flags);
con = readl(host->base + S3C2410_SDICON);
host->sdio_irqen = enable;
if (enable == host->sdio_irqen)
goto same_state;
if (enable) {
con |= S3C2410_SDICON_SDIOIRQ;
enable_imask(host, S3C2410_SDIIMSK_SDIOIRQ);
if (!host->irq_state && !host->irq_disabled) {
host->irq_state = true;
enable_irq(host->irq);
}
} else {
disable_imask(host, S3C2410_SDIIMSK_SDIOIRQ);
con &= ~S3C2410_SDICON_SDIOIRQ;
if (!host->irq_enabled && host->irq_state) {
disable_irq_nosync(host->irq);
host->irq_state = false;
}
}
writel(con, host->base + S3C2410_SDICON);
same_state:
local_irq_restore(flags);
s3cmci_check_sdio_irq(host);
}
第7行local_irq_save(flags)執行兩個動作:1、保存當前中斷標誌到flag;2、禁止本地中斷。之後local_irq_restore(flags)執行相反的操作,開中斷、恢復中斷標誌。
15~33行,讀寫SDICON、SDIIMSK寄存器配置sdio的開啓/禁止。
38行的s3cmci_check_sdio_irq(host)的檢測作用是,在關閉中斷的這段時間內,是否有新中斷產生;有的話自然需要去處理。
request,host向card發送一個請求,在“ SDIO驅動(6)命令的構建和發送”中,發送的結尾就是調用的request回調,即s3cmci_request函數。這裏就涉及硬件的的操作了:
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);
}
3行,s3cmci_host和mmc_host的關係在“SDIO驅動(8)Host驅動實現”說過,這裏略過。
4、5行,mmc_request、mmc_command、mmc_data三者之間經常互相“指來指去”,所以有必要搞明白它們的關係:
7、8行,ccnt命令計數,dcnt數據計數,純粹的計數功能用於log調試,prepare_dbgmsg函數會打印它們的值。
13~15,清除命令狀態寄存器、數據狀態寄存器、fifo狀態寄存器,清除方式就是往相應位寫1。
17~44行的分支用於發送數據,另說。
47行,s3cmci_send_command發送命令:
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;
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行,imsk用於標誌某個中斷的禁止/開啓,這裏:
S3C2410_SDIIMSK_CRCSTATUS:如果CRC狀態錯誤,產生中斷
S3C2410_SDIIMSK_CMDTIMEOUT:如果命令響應超時(timeout),產生中斷
S3C2410_SDIIMSK_RESPONSEND:如果收到卡的響應(response),產生中斷
S3C2410_SDIIMSK_CMDSENT:如果命令已發送,產生中斷
9行,enable_imask啓用imsk標誌的中斷事件,一旦某個事件產生,事件對應寄存器的相應位會被設置。
11~16行,設置事情完成後期望獲得響應的類型。
17行,SDICMDARG命令參數寄存器,保存參數值。
20~29行,設置SDICMDCON命令控制寄存器。S3C2410_SDICMDCON_CMDSTART標誌立即開始命令的發送。
這樣,一條完整的command就由host發送出去了。