SDIO驅動(10)Host的operations實現

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_requestmmc_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發送出去了。

發佈了168 篇原創文章 · 獲贊 150 · 訪問量 56萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章