sdio 驅動

1 確定關注範圍

根據公司代碼編譯出的原始.o 文件,可以間接確定真正起作用的源文件包括(也可以通過 Makefile 來直接確定)。

1) <kernel/driver/mmc/card> 

card目錄下的驅動文件是卡的設備驅動,也就是針對mmc或者sd卡的塊設備驅動

block.c queue.c mmc_test.c sdio_card.c

<block.c>

static int __init mmc_blk_init(void)
{
...
        res = register_blkdev(MMC_BLOCK_MAJOR, "mmc");
...
        res = mmc_register_driver(&mmc_driver);
...
}

register_blkdev爲註冊一個塊設備,mmc_register_driver爲註冊一個mmc驅動。mmc_driver中最重要的函數:mmc_blk_probe,用於找到匹配的mmc_card。

<card.h>

struct mmc_card 
{
        struct mmc_host         *host;
...
}


host 指針指向一個 mmc 主機實例,塊設備中的讀寫操作就是調用這個mmc主機的操作函數host->ops->request來實現對實際硬件的操作。要找到這mmc_card,就得先把mmc_card這個設備掛載到mmc_bus去。mmc_bus 的註冊在第二部分實現,mmc_card的掛載在第三部分實現。


2) <kernel/driver/mmc/core>

core 目錄下的驅動文件是 mmc 總線驅動程序

core.c host.c mmc.c quirks.c sdio_cis.c sdio_irq.c sdio_ops.c sd_ops.c bus.c debugfs.c mmc_ops.c sdio_bus.c sdio_io.c sdio.c sd.c

<core.c>

static int __init mmc_init(void)
{
...
        workqueue = alloc_ordered_workqueue("kmmcd", 0);
...
        ret = mmc_register_bus();
...
        ret = mmc_register_host_class();
...
        ret = sdio_register_bus();
...
}

這個函數一開始建立了一個工作隊列workqueue,這個工作隊列的作用主要是用來支持熱插拔;然後分別註冊一個mmc總線mmc_bus_type,一個mmc_host類,一個 sdio總線 sdio_bus_type


3) < kernel/driver/mmc/host>

host目錄下的驅動文件是mmc或者sd卡的通訊接口驅動

scxxxx.c sdhci.c

<scxxxx.c>

static int __init sdhci_sprd_init(void)
{
        return platform_driver_register(&sdhci_sprd_driver);
}


該函數只是註冊了一個 platform 總線上的驅動,由於我們的 mmc 是通過 platform 總線上的設備 mmc_host 接口來通訊的,所以這個驅動實則上就是最底層的硬件接口驅動。當然,在這之前,我們得註冊一個 platform 總線,並在該 platform 總線上掛載了一個 mmc_host 接口設備。這樣,在註冊 platform 驅動的時候,就會調用該驅動中的 probe 函數。Probe函數中調用了函數:

host = sdhci_alloc_host(dev, sizeof(struct sprd_host_data));//申請 private 結構體 sprd_host_data 
host_data = sdhci_priv(host);//賦值爲 host->private


sdhci_host結構體的最後一個成員爲:unsigned long private[0] , private 指向sdhci_host結構體之後的地址,private本身並不分配空間。所以,通過host->private即可訪問host_data.此爲內核中的常用機制。
進一步調用:
mmc = mmc_alloc_host(sizeof(struct sdhci_host) + priv_size, dev);

創建一個mmc_host結構,這個結構就是在mmc_card所需要的mmc主機實例。
host = mmc_priv(mmc);
host->mmc = mmc;

在mmc_alloc_host中,創建一個mmc_host和 sdhci_host 和 sprd_host_data,且mmc_host的最後一個成員指針private指向 sdhci_host; sdhci_host 的 mmc指針指向mmc_host。
除此之外,mmc_alloc_host()最重要的作用是建立了一個工作隊列任務
INIT_DELAYED_WORK(&host->detect, mmc_rescan);

工作隊列任務執行的函數爲mmc_rescan,在這個函數中以不同的頻率掃描:
static int mmc_rescan_try_freq(struct mmc_host *host, unsigned freq)
{
...
        mmc_power_up(host);
...
        mmc_hw_reset_for_init(host);
...
        sdio_reset(host);
        mmc_go_idle(host);




        mmc_send_if_cond(host, host->ocr_avail);
...
        if (!mmc_attach_sdio(host))
                return 0;




        if (!host->ios.vdd)
                mmc_power_up(host);




        if (!mmc_attach_sd(host))
                return 0;




        if (!host->ios.vdd)
                mmc_power_up(host);




        if (!mmc_attach_mmc(host))
                return 0;




        mmc_power_off(host);
...
}



從這個函數看到,一開始就是設置某一個時鐘頻率,然後對mmc或者sd發送一些命令進行探測。
接着看mmc_attach_mmc()
int mmc_attach_mmc(struct mmc_host *host)
{
...
err = mmc_init_card(host, host->ocr, NULL);
...
err = mmc_add_card(host->card);
...
}


一開始設置mmc的電壓,然後就對mmc卡進行初始化,主要是讀取mmc卡里的一些寄存器信息,且對這些寄存器的值進行設置。然後,調用mmc_add_card來把mmc_card掛載到mmc_bus_type總線上去:
int mmc_add_card(struct mmc_card *card)
{
...
ret = device_add(&card->dev);
...
}


接着調用 device_add()
int device_add(struct device *dev)
{
...
bus_probe_device(dev);
...
}


然後調用bus_probe_device()
void bus_probe_device(struct device *dev)
{
...
ret = device_attach(dev);
...
}


這樣,在總線mmc_bus_type中就有了mmc設備mmc_card了。


probe 函數的一個重要初始化操作:
host->ops = &sdhci_sprd_ops;


這個 sdhci_sprd_ops 操作函數就是 sdhci 接口的操作函數。


不過這裏有個問題,就是這個工作隊列任務什麼時候開始執行呢,又與 (1) 部分創建的工作隊列workqueue有什麼關係,跟熱插拔有什麼關係。

繼續把焦點放到probe函數:

{
...
host_data->platdata = dev_get_platdata(dev);
...

sd_detect_gpio = host_data->platdata->detect_gpio;
...

detect_irq = gpio_to_irq(sd_detect_gpio);
...

host_data->detect_irq = detect_irq;
...
ret = sdhci_add_host(host);
...
}



這裏有個 detect_irq 的變量,保存了設備中斷引腳號。當sd卡插入時檢測到一個外部中斷。

int sdhci_add_host(struct sdhci_host *host)
{
...
host_data = sdhci_priv(host);
detect_irq = host_data->detect_irq;
if (sdcard_present(host))
	{
		ret = request_threaded_irq(detect_irq, NULL, sd_detect_irq,
			IRQF_TRIGGER_HIGH | IRQF_ONESHOT, "sd card detect", host);
	} 
else 
	{
		ret = request_threaded_irq(detect_irq, NULL, sd_detect_irq,
			IRQF_TRIGGER_LOW | IRQF_ONESHOT, "sd card detect", host);
	}
...
sdhci_init(host, 0);
...
mmc_add_host(mmc);
...
sdhci_enable_card_detection(host);
...
}

打開SD卡檢測

static void sdhci_enable_card_detection(struct sdhci_host *host)
{
	sdhci_set_card_detection(host, true);
}
進入sdhci_set_card_detection()
static void sdhci_set_card_detection(struct sdhci_host *host, bool enable)
{
	int irq;
	struct sprd_host_data *host_data;

	host_data = sdhci_priv(host);
	irq = host_data->detect_irq;
	if(irq > 0){
		if(!enable) {
			irq_set_irq_type(irq,IRQF_TRIGGER_NONE);
			return;
		}

		if(sdcard_present(host)){
			irq_set_irq_type(irq,IRQF_TRIGGER_HIGH);
		}else{
			irq_set_irq_type(irq,IRQF_TRIGGER_LOW);
		}
	}
}


<block.c> module_init(mmc_blk_init);
<mmc_test.c> module_init(mmc_test_init);
<scxxxx.c> module_init(sdhci_sprd_init);
<sdhci.c> module_init(sdhci_drv_init);










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