歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
您现在的位置: Linux教程網 >> UnixLinux >  >> Linux基礎 >> Linux技術

Linux SD卡驅動開發(二) —— SD 卡驅動分析HOST篇

回顧一下前面的知識,MMC 子系統范圍三個部分:
HOST 部分是針對不同主機的驅動程序,這一部是驅動程序工程師需要根據自己的特點平台來完成的。
CORE 部分: 這是整個MMC 的核心存,這部分完成了不同協議和規范的實現,並為HOST 層的驅動提供了接口函數。
CARD 部分:因為這些記憶卡都是塊設備,當然需要提供塊設備的驅動程序,這部分就是實現了將你的SD 卡如何實現為塊設備的。
它們分布於下面的文件夾中 Linux/drivers/mmc中

其中,card(區塊層) 與core(核心層)是linux系統封裝好了部分,我們不需要修改,host(主控制器層)中提供與各芯片構架相關的文件,這才是我們所要開發的部分
核心層根據需要構造各種MMC/SD命令,這些命令怎麼發送給MMC/SD卡呢?這通過主機控制器層來實現。這層是架構相關的,裡面針對各款CPU提供一個文件,目前支持的CPU還很少。
以本節即將移植的s3cmci.c為例,它首先進行一些低層設置,比如設置MMC/SD/SDIO控制器使用到的CPIO引腳、使能控制器、注冊中斷處理函數等,然後向上面的核心層增加一個主機(Host),這樣核心層就能調用s3cmci.c提供的函數來識別、使用具體存儲卡了。
在向核心層增加主機之前,s3cmci.c 設置了一個mmc_host_ops結構體,它實現兩個函數:
a -- 發起訪問請求的request函數
b -- 進行一些屬性設置(時鐘頻率、數據線位寬等)的set_ios函數
下面列出識別存儲卡、區塊層發起操作請求兩種情況下函數的主要調用關系:
1)識別存儲卡




2)區塊層發起操作請求



以後上次對存儲卡的操作都通過調用這兩個函數來完成。下面對HOST層進行分析(Linux內核版本:Linux-3.14)。
一、struct mmc_host 結構體
主要用來描述卡控制器位, 結構體mmc_host定義於/include/linux/mmc/host.c,可以認為是linux為SD卡控制器專門准備的一個類,該類裡面的成員是所有SD卡控制器都需要的,放之四海而皆准的數據結構,在本例芯片控制器的驅動程序s3cmci.c中,則為該類具體化了一個對象struct
mmc_host *mmc
,此mmc指針即指代著該ARM芯片SD卡控制器的一個具體化對象(可以看出雖然C是面向過程的語言,但還是用到了一些面向對象思想的)。
在linux/driver/mmc/host/s3cmci.h下定義
[cpp] view
plain copy





struct s3cmci_host {
struct platform_device *pdev;
struct s3c24xx_mci_pdata *pdata;
struct mmc_host *mmc;
struct resource *mem;
struct clk *clk;
void __iomem *base;
int irq;
int irq_cd;
int dma;
unsigned long clk_rate;
unsigned long clk_div;
unsigned long real_rate;
u8 prescaler;
int is2440;
unsigned sdiimsk;
unsigned sdidata;
int dodma;
int dmatogo;
bool irq_disabled;
bool irq_enabled;
bool irq_state;
int sdio_irqen;
struct mmc_request *mrq;
int cmd_is_stop;
spinlock_t complete_lock;
enum s3cmci_waitfor complete_what;
int dma_complete;
u32 pio_sgptr;
u32 pio_bytes;
u32 pio_count;
u32 *pio_ptr;
#define XFER_NONE 0
#define XFER_READ 1
#define XFER_WRITE 2
u32 pio_active;
int bus_width;
char dbgmsg_cmd[301];
char dbgmsg_dat[301];
char *status;
unsigned int ccnt, dcnt;
struct tasklet_struct pio_tasklet;
#ifdef CONFIG_DEBUG_FS
struct dentry *debug_root;
struct dentry *debug_state;
struct dentry *debug_regs;
#endif
#ifdef CONFIG_CPU_FREQ
struct notifier_block freq_transition;
#endif
};
其中struct mmc_host(linux/include/linux/mmc/host.h)用於與core層的命令請求,數據 傳輸等信息,這裡代碼較長,展示部分
[cpp] view
plain copy





struct mmc_host
{
const struct mmc_host_ops *ops; // SD卡主控制器的操作函數,即該控制器所具備的驅動能力
const struct mmc_bus_ops *bus_ops; // SD總線驅動的操作函數,即SD總線所具備的驅動能力
struct mmc_ios ios; // 配置時鐘、總線、電源、片選、時序等
struct mmc_card *card; // 連接到此主控制器的SD卡設備
... ...
};
本文中struct mmc_host_ops *ops定義
[cpp] view
plain copy





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,
};
二、SD控制器之初始化(linux/driver/mmc/host)
這一層講述硬件與硬件之間將要發生的故事,也是最底層驅動的核心。通常所謂的驅動程序設計的任務將落實到這一層上,所以關注host故事的發展也將成為移植整個SD類設備驅動的核心。在host 目錄中有各種平台下SD 卡主機驅動器的實例,這裡我們選擇s3c2440平台作為分析的重點。參看Kconfig和Makefile即可獲得相應信息,這裡對應的文件即是s3cmci.c。
1、設備的注冊
舊瓶裝新酒,還是那個module_init,不一樣的是其中的入口函數。在s3cmci.c中對應的是module_init(s3cmci_init);
[cpp] view
plain copy





module_platform_driver(s3cmci_driver);
這裡是不是很奇怪,不應該是module_init 與 module_exit 嗎?module_platform_driver其實是一個宏定義,定義在include/linux/platform_device.h文件中:
[cpp] view
plain copy





/* module_platform_driver() - Helper macro for drivers that don't do
* anything special in module init/exit. This eliminates a lot of
* boilerplate. Each module may only use this macro once, and
* calling it replaces module_init() and module_exit()
*/
#define module_platform_driver(__platform_driver) \
module_driver(__platform_driver, platform_driver_register, \
platform_driver_unregister)
宏module_driver定義在include/linux/device.h文件中,其內容如下:
[cpp] view
plain copy





/**
* module_driver() - Helper macro for drivers that don't do anything
* special in module init/exit. This eliminates a lot of boilerplate.
* Each module may only use this macro once, and calling it replaces
* module_init() and module_exit().
*
* @__driver: driver name
* @__register: register function for this driver type
* @__unregister: unregister function for this driver type
* @...: Additional arguments to be passed to __register and __unregister.
*
* Use this macro to construct bus specific macros for registering
* drivers, and do not use it on its own.
*/
#define module_driver(__driver, __register, __unregister, ...) \
static int __init __driver##_init(void) \
{ \
return __register(&(__driver) , ##__VA_ARGS__); \
} \
module_init(__driver##_init); \
static void __exit __driver##_exit(void) \
{ \
__unregister(&(__driver) , ##__VA_ARGS__); \
} \
module_exit(__driver##_exit);
可以看出,最終還是調用module_init 與 module_exit。這裡注冊了一個平台驅動,這個前面分析的串口驅動等是一樣的原理。這是新版內核中引入的一個虛擬的平台總線。對應的平台設備早在內核啟動時通過platform_add_devices加入到了內核,相關的具體內容前面已經分析的挺多了,這裡就不在詳細說明。該句調用的結果會導致s3cmci_driver中的probe方法得以調用,由此也就把我們引入了host的世界。
2、probe函數
在驅動的接口函數中s3cmci_probe() 函數,用於分配mmc_host ,s3cmci_host結構體,並對結構體進行設置,在SDI主機控制器操作接口函數mmc_host_ops中會調用s3cmci_host結構體,申請中斷並設置中斷服務函數,將結構體mmc_host添加到主機。
SD主控制器驅動程序的初始化函數probe(struct platform_device *pdev),概括地講,主要完成五大任務,
初始化設備的數據結構,並將數據掛載到pdev->dev.driver_data下
實現設備驅動的功能函數,如mmc->ops = &s3cmci_ops;
申請中斷函數request_irq()
注冊設備,即注冊kobject,建立sys文件,發送uevent等
其他需求,如在/proc/driver下建立用戶交互文件等
首先還是來大致看一下s3cmci_driver中所對應的具體內容
[cpp] view
plain copy





static struct platform_driver s3cmci_driver = {
.driver = {
.name = "s3c-sdi",
.owner = THIS_MODULE,
},
.id_table = s3cmci_driver_ids,
.probe = s3cmci_probe,
.remove = s3cmci_remove,
.shutdown = s3cmci_shutdown,
};
其中probe是我們關注的核心,由於host與硬件是直接相關的,probe接下來將做部分關於硬件初始化的工作,因此在分析下面一部分代碼之前,最好能對s3c2440的sdi相關的內容有所了解。下面進入到probe的相關內容,整個函數洋洋灑灑三百多行,就分段說明吧。
先看這幾行
[cpp] view
plain copy





static int s3cmci_probe(struct platform_device *pdev)
{
struct s3cmci_host *host;
struct mmc_host *mmc;
int ret;
int is2440;
int i;
is2440 = platform_get_device_id(pdev)->driver_data;
mmc = mmc_alloc_host(sizeof(struct s3cmci_host), &pdev->dev);
if (!mmc) {
ret = -ENOMEM;
goto probe_out;
}
這裡,mmc = mmc_alloc_host(sizeof(struct s3cmci_host), &pdev->dev); 分配一個mmc的控制器,同時struct s3cmci_host 結構作為一個私有數據類型將添加到struct mmc_host的private域。
a -- mmc_alloc_host
mmc_alloc_host相應的代碼如下:linux/drivers/mmc/core/host.c
[cpp] view
plain copy





struct mmc_host *mmc_alloc_host(int extra, struct device *dev)
{
int err;
struct mmc_host *host;
host = kzalloc(sizeof(struct mmc_host) + extra, GFP_KERNEL);
if (!host)
return NULL;
/* scanning will be enabled when we're ready */
host->rescan_disable = 1;
idr_preload(GFP_KERNEL);
spin_lock(&mmc_host_lock);
err = idr_alloc(&mmc_host_idr, host, 0, 0, GFP_NOWAIT);
if (err >= 0)
host->index = err;
spin_unlock(&mmc_host_lock);
idr_preload_end();
if (err < 0)
goto free;
dev_set_name(&host->class_dev, "mmc%d", host->index);
host->parent = dev;
host->class_dev.parent = dev;
host->class_dev.class = &mmc_host_class;
device_initialize(&host->class_dev);
mmc_host_clk_init(host);
mutex_init(&host->slot.lock);
host->slot.cd_irq = -EINVAL;
spin_lock_init(&host->lock);
init_waitqueue_head(&host->wq);
INIT_DELAYED_WORK(&host->detect, mmc_rescan);
#ifdef CONFIG_PM
host->pm_notify.notifier_call = mmc_pm_notify;
#endif
/*
* By default, hosts do not support SGIO or large requests.
* They have to set these according to their abilities.
*/
host->max_segs = 1;
host->max_seg_size = PAGE_CACHE_SIZE;
host->max_req_size = PAGE_CACHE_SIZE;
host->max_blk_size = 512;
host->max_blk_count = PAGE_CACHE_SIZE / 512;
return host;
free:
kfree(host);
return NULL;
}
14行是內核的高效搜索樹,將host->index與host結構相關聯,方便以後查找。
24-27行主要是初始化host->class_dev,這個日後會通過device_register注冊進系統。
35行前面已經在這個等待隊列上花了不少筆墨,主要是同步對host資源的競爭的。
45-50行這些個都是設置host的一些屬性的,是與block.c中請求隊列的設置相對應的。
重新回到s3cmci_probe....
[cpp] view
plain copy





for (i = S3C2410_GPE(5); i <= S3C2410_GPE(10); i++) {
ret = gpio_request(i, dev_name(&pdev->dev));
if (ret) {
dev_err(&pdev->dev, "failed to get gpio %d\n", i);
for (i--; i >= S3C2410_GPE(5); i--)
gpio_free(i);
goto probe_free_host;
}
}
上面這一段申請SD卡驅動器所需的GPIO資源。
繼續分析...
[cpp] view
plain copy





host = mmc_priv(mmc);
host->mmc = mmc;
host->pdev = pdev;
host->is2440 = is2440;
上面這段代碼就是對struct s3cmci_host *host這個私有結構 的配置,對於core或block層見到的只有struct
mmc_host
。從另外的一個角度可以理解struct mmc_host實際上是struct s3cmci_host的基類,有著所有控制器所必須具有的屬性。struct
s3cmci_host還包含了與host硬件平台相關的特征。
[cpp] view
plain copy





host->pdata = pdev->dev.platform_data;
if (!host->pdata) {
pdev->dev.platform_data = &s3cmci_def_pdata;
host->pdata = &s3cmci_def_pdata;
}
這是平台設備注冊時platform_device所具有的屬性.
[cpp] view
plain copy





spin_lock_init(&host->complete_lock);
_init(&host->pio_tasklet, pio_tasklet, (unsigned long) host);
tasklet就象一個內核定時器, 在一個"軟中斷"的上下文中執行(以原子模式),常用在硬件中斷處理中,使得可以使得復雜的任務安全地延後到以後的時間處理。task_init 建立一個tasklet,然後調用函數 tasklet_schedule將這個tasklet放在 tasklet_vec鏈表的頭部,並喚醒後台線程 ksoftirqd。當後台線程ksoftirqd
運行調用__do_softirq 時,會執行在中斷向量表softirq_vec裡中斷號TASKLET_SOFTIRQ對應的 tasklet_action函數,然後 tasklet_action遍歷 tasklet_vec鏈表,調用每個 tasklet的函數完成軟中斷操作,上面例子中即是pio_tasklet函數,另外軟中斷處理函數只能傳遞一個long型變量。這裡是直接使用host的地址,作為傳遞參數。關於這個pio_tasklet現在說他還為時過早,等時辰一到自然會對他大書特書。
[cpp] view
plain copy





if (is2440) {
host->sdiimsk = S3C2440_SDIIMSK;
host->sdidata = S3C2440_SDIDATA;
host->clk_div = 1;
} else {
host->sdiimsk = S3C2410_SDIIMSK;
host->sdidata = S3C2410_SDIDATA;
host->clk_div = 2;
}
host->sdiimsk 、host->sdidata分別用來存放host控制器SDI中斷屏蔽寄存器SDI數據寄存器相對SDI 寄存器的偏移地址。對於s3c2440 根據芯片手冊SDIIntMsk 偏移地址為0x3C,SDIDAT偏移地址為0x40。最後host->clk_div就是指的SDI使用的時鐘分頻系數了。
[cpp] view
plain copy





host->complete_what = COMPLETION_NONE;
t;pio_active = XFER_NONE;
host->complete_what 是一個枚舉類型變量,實際上用以標示傳輸完成的狀態;host->pio_active標示數據傳輸的方向,所以在這裡一起初始化為空。關於傳輸完成的標示為了後面分析方便還是一起列舉出來:
[cpp] view
plain copy





enum s3cmci_waitfor {
COMPLETION_NONE,
COMPLETION_FINALIZE,
COMPLETION_CMDSENT,
COMPLETION_RSPFIN,
COMPLETION_XFERFINISH,
COMPLETION_XFERFINISH_RSPFIN,
};
繼續分析
[cpp] view
plain copy





#ifdef CONFIG_MMC_S3C_PIODMA
host->dodma = host->pdata->use_dma;
#endif
上面是同時使能了PIO和DMA模式的情況,這裡我們對兩種傳輸方式都做相應的分析,所以host->dodma默認為1。
[cpp] view
plain copy





host->mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!host->mem) {
dev_err(&pdev->dev,
"failed to get io memory region resource.\n");
ret = -ENOENT;
goto probe_free_gpio;
}
host->mem = request_mem_region(host->mem->start,
resource_size(host->mem), pdev->name);
if (!host->mem) {
dev_err(&pdev->dev, "failed to request io memory region.\n");
ret = -ENOENT;
goto probe_free_gpio;
}
host->base = ioremap(host->mem->start, resource_size(host->mem));
if (!host->base) {
dev_err(&pdev->dev, "failed to ioremap() io memory region.\n");
ret = -EINVAL;
goto probe_free_mem_region;
}
上面的一段代碼相對比較簡單,都是平台驅動設計過程中常用的幾個處理函數,就不一一展開了。首先是獲取IO 資源,
這當然即使mach-mini2440.c 中所注冊的IORESOURCE_MEM,
這段代碼會申請這個資源並檢查是否可用,當然只要之前沒有使用過SDI寄存器空間,這裡都會申請成功。最後就是IO映射,將實地址映射為內核虛擬地址使用。最後,host->base將保持SDI寄存器基地址所對應的內核虛擬地址。
[cpp] view
plain copy





host->irq = platform_get_irq(pdev, 0);
if (host->irq == 0) {
dev_err(&pdev->dev, "failed to get interrupt resource.\n");
ret = -EINVAL;
goto probe_iounmap;
}
if (request_irq(host->irq, s3cmci_irq, 0, DRIVER_NAME, host)) {
dev_err(&pdev->dev, "failed to request mci interrupt.\n");
ret = -ENOENT;
goto probe_iounmap;
}
上面一段是對中斷資源的申請,並通過request_irq安裝了中斷處理函數,使能了SDI中斷。在上段最為關心的是s3cmci_irq 中斷處理函數及其傳入的dev_id。關於這個處理函數的分析後面講述數據傳輸的時候會進行細致分析。接著向下....
[cpp] view
plain copy





/* We get spurious interrupts even when we have set the IMSK
* register to ignore everything, so use disable_irq() to make
* ensure we don't lock the system with un-serviceable requests. */
disable_irq(host->irq);
host->irq_state = false;
前面我們強調了request_irq調用的結果會使能host->irq,但此時系統初始化尚未完成這時候出現的中斷可能將處理器帶入一個異常狀態,所以5行屏蔽中斷1650行將中斷狀態置位無效都是有必要的。
[cpp] view
plain copy





if (!host->pdata->no_detect) {
ret = gpio_request(host->pdata->gpio_detect, "s3cmci detect");
if (ret) {
dev_err(&pdev->dev, "failed to get detect gpio\n");
goto probe_free_irq;
}
如果SD卡存在的檢查一般是通過讀取專用引腳狀態來實現的,這裡如果需要做detect相關的工作的話就必須重新分配一個管腳,在當前系統中前面定義了以GPG8作為檢測引腳
[cpp] view
plain copy





host->irq_cd = gpio_to_irq(host->pdata->gpio_detect);
s3c2410_gpio_getirq是獲取這個GPIO的外部中斷向量號,可見後面的SD卡的檢測可能會用到這個引腳的外部中斷。
[cpp] view
plain copy





if (host->irq_cd >= 0) {
if (request_irq(host->irq_cd, s3cmci_irq_cd,
IRQF_TRIGGER_RISING |
IRQF_TRIGGER_FALLING,
DRIVER_NAME, host)) {
dev_err(&pdev->dev,
"can't get card detect irq.\n");
ret = -ENOENT;
goto probe_free_gpio_cd;
}
} else {
dev_warn(&pdev->dev,
"host detect has no irq available\n");
gpio_direction_input(host->pdata->gpio_detect);
}
} else
host->irq_cd = -1;
上面的這段代碼意圖很明顯就是要申請這個中斷了,s3cmci_irq_cd是其處理函數。對像SD卡這種可移出設備來作為塊設備存儲介質的話,大多會涉及到媒體切換,具體這方面的內容後面用到的時候也會有個詳細分析。
[cpp] view
plain copy





if (!host->pdata->no_wprotect) {
ret = gpio_request(host->pdata->gpio_wprotect, "s3cmci wp");
if (ret) {
dev_err(&pdev->dev, "failed to get writeprotect\n");
goto probe_free_irq_cd;
}
gpio_direction_input(host->pdata->gpio_wprotect);
}
/* depending on the dma state, get a dma channel to use. */
if (s3cmci_host_usedma(host)) {
host->dma = s3c2410_dma_request(DMACH_SDI, &s3cmci_dma_client,
host);
if (host->dma < 0) {
dev_err(&pdev->dev, "cannot get DMA channel.\n");
if (!s3cmci_host_canpio()) {
ret = -EBUSY;
goto probe_free_gpio_wp;
} else {
dev_warn(&pdev->dev, "falling back to PIO.\n");
host->dodma = 0;
}
}
}
1行這個我們之前就默認為TURE了,所以接下來的幾行代碼是免不了。
14 行s3c2410_dma_request 申請DMA 通道,對s3c2440 平台有4 通道的DMA。而DMACH_SDI 是一個虛擬的通道號,由於這部分代碼是和硬件緊密相關的,而且整個DMA的管理相對來講比較復雜,所以這裡只是粗略了解一下。
[cpp] view
plain copy





host->clk = clk_get(&pdev->dev, "sdi");
if (IS_ERR(host->clk)) {
dev_err(&pdev->dev, "failed to find clock source.\n");
ret = PTR_ERR(host->clk);
host->clk = NULL;
goto probe_free_dma;
}
ret = clk_enable(host->clk);
if (ret) {
dev_err(&pdev->dev, "failed to enable clock source.\n");
goto clk_free;
}
host->clk_rate = clk_get_rate(host->clk);
以上是關於sdi時鐘和波特率的有關設置,都是內核提供的一些簡單的函數調用,相關的內容不是現在研究的重點就不再詳細分析了。
[cpp] view
plain copy





mmc->ops = &s3cmci_ops;
mmc->ocr_avail = MMC_VDD_32_33 | MMC_VDD_33_34;
#ifdef CONFIG_MMC_S3C_HW_SDIO_IRQ
mmc->caps = MMC_CAP_4_BIT_DATA | MMC_CAP_SDIO_IRQ;
#else
mmc->caps = MMC_CAP_4_BIT_DATA;
#endif
mmc->f_min = host->clk_rate / (host->clk_div * 256);
mmc->f_max = host->clk_rate / host->clk_div;
if (host->pdata->ocr_avail)
mmc->ocr_avail = host->pdata->ocr_avail;
mmc->max_blk_count = 4095;
mmc->max_blk_size = 4095;
mmc->max_req_size = 4095 * 512;
mmc->max_seg_size = mmc->max_req_size;
mmc->max_segs = 128;
dbg(host, dbg_debug,
"probe: mode:%s mapped mci_base:%p irq:%u irq_cd:%u dma:%u.\n",
(host->is2440?"2440":""),
host->base, host->irq, host->irq_cd, host->dma);
上面就是對這個將要出嫁的mmc_host進行最後的設置mmc->ops = &s3cmci_ops; 就是一直以來向core層提供的接口函數集。後面的分析可能部分是圍繞它其中的函數展開的。
[cpp] view
plain copy





ret = s3cmci_cpufreq_register(host);
if (ret) {
dev_err(&pdev->dev, "failed to register cpufreq\n");
goto free_dmabuf;
}
這裡使用的是Linux的通告機制,s3cmci_cpufreq_register相對應的代碼如下:
static inline int s3cmci_cpufreq_register(struct s3cmci_host *host) {
host->freq_transition.notifier_call = s3cmci_cpufreq_transition;
return cpufreq_register_notifier(&host->freq_transition,
CPUFREQ_TRANSITION_NOTIFIER);
}
cpufreq_register_notifier cpu是將host->freq_transition注冊到CPU頻率通告鏈上,這個是由內核維護的,當cpu頻率改變時將會調用上面注冊的s3cmci_cpufreq_transition的內容。
[cpp] view
plain copy





ret = mmc_add_host(mmc);
if (ret) {
dev_err(&pdev->dev, "failed to add mmc host.\n");
goto free_cpufreq;
}
b -- mmc_add_host
這是core層的函數,他保存了所有平台通用的代碼。同時看上去簡簡單單的一個mmc_add_host,可能蘊藏著天大的玄機。為此我們將為mmc_add_host另分一章,作為core層的續集,專門講述mmc_add_host過程中發生的點點滴滴....在開始分析mmc_add_host之前,讓我們還是結束SD主機控制器的probe函數,接下來
[cpp] view
plain copy





s3cmci_debugfs_attach(host);
platform_set_drvdata(pdev, mmc);
dev_info(&pdev->dev, "%s - using %s, %s SDIO IRQ\n", mmc_hostname(mmc),
s3cmci_host_usedma(host) ? "dma" : "pio",
mmc->caps & MMC_CAP_SDIO_IRQ ? "hw" : "sw");
return 0;
free_cpufreq:
s3cmci_cpufreq_deregister(host);
free_dmabuf:
clk_disable(host->clk);
clk_free:
clk_put(host->clk);
probe_free_dma:
if (s3cmci_host_usedma(host))
s3c2410_dma_free(host->dma, &s3cmci_dma_client);
probe_free_gpio_wp:
if (!host->pdata->no_wprotect)
gpio_free(host->pdata->gpio_wprotect);
probe_free_gpio_cd:
if (!host->pdata->no_detect)
gpio_free(host->pdata->gpio_detect);
probe_free_irq_cd:
if (host->irq_cd >= 0)
free_irq(host->irq_cd, host);
probe_free_irq:
free_irq(host->irq, host);
probe_iounmap:
iounmap(host->base);
probe_free_mem_region:
release_mem_region(host->mem->start, resource_size(host->mem));
probe_free_gpio:
for (i = S3C2410_GPE(5); i <= S3C2410_GPE(10); i++)
gpio_free(i);
probe_free_host:
mmc_free_host(mmc);
probe_out:
return ret;
}
static void s3cmci_shutdown(struct platform_device *pdev)
{
struct mmc_host *mmc = platform_get_drvdata(pdev);
struct s3cmci_host *host = mmc_priv(mmc);
if (host->irq_cd >= 0)
free_irq(host->irq_cd, host);
s3cmci_debugfs_remove(host);
s3cmci_cpufreq_deregister(host);
mmc_remove_host(mmc);
clk_disable(host->clk);
}
上面的基本都是出錯的處理了,那麼也就沒有必要糾結於此了,趕快開啟新的生活吧....
再多說一句,可以看到probe函數裡面,大量的工作是對 host 結構體 mmc結構體的填充,類似於USB probe函數中對 usb_skel 結構體的填充,向上提供接口函數。
三、 設備驅動的功能函數
一般情況下,設備驅動裡都有一個行為函數結構體,比如字符設備驅動裡的struct file_operations *fops,該結構描述了設備所具備的工作能力,比如open, read, write等,
struct file_operations {
struct module *owner;
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
int (*open) (struct inode *, struct file *);
... ...
};
同理,SD主控制器驅動程序裡也有一個類似的結構struct mmc_host_ops *ops,它描述了該控制器所具備驅動的能力。
[cpp] view
plain copy





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,
};
其中,(*set_ios)為主控制器設置總線和時鐘等配置,(*get_ro)得到只讀屬性(*enable_sdio_irq)開啟sdio中斷,本文重點討論(*request)這個回調函數,它是整個SD主控制器驅動的核心,實現了SD主控制器能與SD卡進行通信的能力。
[cpp] view
plain copy





static void s3cmci_request(struct mmc_host *mmc, struct mmc_request *mrq)
{
struct s3cmci_host *host = mmc_priv(mmc);
host->status = "mmc request";
host->cmd_is_stop = 0;
host->mrq = mrq;
if (s3cmci_card_present(mmc) == 0) {
dbg(host, dbg_err, "%s: no medium present\n", __func__);
host->mrq->cmd->error = -ENOMEDIUM;
mmc_request_done(mmc, mrq);
} else
s3cmci_send_request(mmc);
}
可以看到這裡調用了s3cmci_send_request(mmc) 函數,定義如下:
[cpp] view
plain copy





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);
}
其中有兩個重要的結構體 s3cmci_setup_data(host, cmd->data)實現數據傳輸 及s3cmci_send_command(host, cmd)實現指令傳輸,至此,我們需要接觸SD主控制器的芯片手冊了。
首先,SD主控制器由一系列32位寄存器組成。通過軟件的方式,即對寄存器賦值,來控制SD主控制器,進而扮演SD主控制器的角色與SD卡取得通信。

1) cmdat
根據主控制器的芯片手冊,寄存器MMC_CMDAT控制命令和數據的傳輸,具體內容如下

結合對寄存器MMC_CMDAT的描述,分析代碼
[cpp] view
plain copy





host->cmdat &= ~CMDAT_INIT; // 非初始化狀態
if (mrq->data) { // 如果存在數據需要傳輸
pxamci_setup_data(host, mrq->data); // 實現主控制器與SD卡之間數據的傳輸
cmdat &= ~CMDAT_BUSY; // 沒有忙碌busy信號
cmdat |= CMDAT_DATAEN | CMDAT_DMAEN; // 有數據傳輸,使用DMA
if (mrq->data->flags & MMC_DATA_WRITE)
cmdat |= CMDAT_WRITE; // 設置為寫數據
if (mrq->data->flags & MMC_DATA_STREAM)
cmdat |= CMDAT_STREAM; // 設置為數據流stream模式
}
2) s3cmci_setup_data
通過DMA實現主控制器與SD卡之間數據的傳輸
[cpp] view
plain copy





static int s3cmci_setup_data(struct s3cmci_host *host, struct mmc_data *data)
{
u32 dcon, imsk, stoptries = 3;
/* write DCON register */
if (!data) {
writel(0, host->base + S3C2410_SDIDCON);
return 0;
}
if ((data->blksz & 3) != 0) {
/* We cannot deal with unaligned blocks with more than
* one block being transferred. */
if (data->blocks > 1) {
pr_warning("%s: can't do non-word sized block transfers (blksz %d)\n", __func__, data->blksz);
return -EINVAL;
}
}
while (readl(host->base + S3C2410_SDIDSTA) &
(S3C2410_SDIDSTA_TXDATAON | S3C2410_SDIDSTA_RXDATAON)) {
dbg(host, dbg_err,
"mci_setup_data() transfer stillin progress.\n");
writel(S3C2410_SDIDCON_STOP, host->base + S3C2410_SDIDCON);
s3cmci_reset(host);
if ((stoptries--) == 0) {
dbg_dumpregs(host, "DRF");
return -EINVAL;
}
}
dcon = data->blocks & S3C2410_SDIDCON_BLKNUM_MASK;
if (s3cmci_host_usedma(host))
dcon |= S3C2410_SDIDCON_DMAEN;
if (host->bus_width == MMC_BUS_WIDTH_4)
dcon |= S3C2410_SDIDCON_WIDEBUS;
if (!(data->flags & MMC_DATA_STREAM))
dcon |= S3C2410_SDIDCON_BLOCKMODE;
if (data->flags & MMC_DATA_WRITE) {
dcon |= S3C2410_SDIDCON_TXAFTERRESP;
dcon |= S3C2410_SDIDCON_XFER_TXSTART;
}
if (data->flags & MMC_DATA_READ) {
dcon |= S3C2410_SDIDCON_RXAFTERCMD;
dcon |= S3C2410_SDIDCON_XFER_RXSTART;
}
if (host->is2440) {
dcon |= S3C2440_SDIDCON_DS_WORD;
dcon |= S3C2440_SDIDCON_DATSTART;
}
writel(dcon, host->base + S3C2410_SDIDCON);
/* write BSIZE register */
writel(data->blksz, host->base + S3C2410_SDIBSIZE);
/* add to IMASK register */
imsk = S3C2410_SDIIMSK_FIFOFAIL | S3C2410_SDIIMSK_DATACRC |
S3C2410_SDIIMSK_DATATIMEOUT | S3C2410_SDIIMSK_DATAFINISH;
enable_imask(host, imsk);
/* write TIMER register */
if (host->is2440) {
writel(0x007FFFFF, host->base + S3C2410_SDITIMER);
} else {
writel(0x0000FFFF, host->base + S3C2410_SDITIMER);
/* FIX: set slow clock to prevent timeouts on read */
if (data->flags & MMC_DATA_READ)
writel(0xFF, host->base + S3C2410_SDIPRE);
}
return 0;
}
for()循環裡的內容是整個SD卡主控制器設備驅動的實質,通過DMA的方式實現主控制器與SD卡之間數據的讀寫操作。
3) m3cmci_start_cmd
實現主控制器與SD卡之間指令的傳輸
[cpp] view
plain copy





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 |
S3C2410_SDIIMSK_RESPONSECRC;
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);
}
> response類型
根據SD卡的協議,當SD卡收到從控制器發來的cmd指令後,SD卡會發出response相應,而response的類型分為R1,R1b,R2,R3,R6,R7,這些類型分別對應不同的指令,各自的數據包結構也不同(具體內容參考SD卡協議)。這裡,通過RSP_TYPE對指令cmd的opcode的解析得到相對應的reponse類型,再通過swich賦給寄存器MMC_CMDAT對應的[1:0]位。
-> 將指令和參數寫入寄存器
4行writel()是整個SD卡主控制器設備驅動的實質,通過對主控制器芯片寄存器MMC_CMD,MMC_ARGH,MMC_ARGL,MMC_CMDAT的設置,實現主控制器發送指令到SD卡的功能。
四、申請中斷
這裡對前面的中斷函數進行詳細描述:
s3cmci_probe(struct platform_device *pdev)中有兩個中斷,一個為SD主控制器芯片內電路固有的內部中斷,另一個為探測引腳的探測到外部有SD卡插拔引起的中斷
1) 由主控芯片內部電路引起的中斷
(request_irq(host->irq, s3cmci_irq, 0, DRIVER_NAME, host);
回顧一下,host->irq就是剛才從platform_device裡得到的中斷號,
irq = platform_get_irq(pdev, 0);
host->irq = irq;
接下來,s3cmci_irq便是為該中斷host->irq申請的中斷函數
[cpp] view
plain copy





/*
* ISR for SDI Interface IRQ
* Communication between driver and ISR works as follows:
* host->mrq points to current request
* host->complete_what Indicates when the request is considered done
* COMPLETION_CMDSENT when the command was sent
* COMPLETION_RSPFIN when a response was received
* COMPLETION_XFERFINISH when the data transfer is finished
* COMPLETION_XFERFINISH_RSPFIN both of the above.
* host->complete_request is the completion-object the driver waits for
*
* 1) Driver sets up host->mrq and host->complete_what
* 2) Driver prepares the transfer
* 3) Driver enables interrupts
* 4) Driver starts transfer
* 5) Driver waits for host->complete_rquest
* 6) ISR checks for request status (errors and success)
* 6) ISR sets host->mrq->cmd->error and host->mrq->data->error
* 7) ISR completes host->complete_request
* 8) ISR disables interrupts
* 9) Driver wakes up and takes care of the request
*
* Note: "->error"-fields are expected to be set to 0 before the request
* was issued by mmc.c - therefore they are only set, when an error
* contition comes up
*/
static irqreturn_t s3cmci_irq(int irq, void *dev_id)
{
struct s3cmci_host *host = dev_id;
struct mmc_command *cmd;
u32 mci_csta, mci_dsta, mci_fsta, mci_dcnt, mci_imsk;
u32 mci_cclear = 0, mci_dclear;
unsigned long iflags;
mci_dsta = readl(host->base + S3C2410_SDIDSTA);
mci_imsk = readl(host->base + host->sdiimsk);
if (mci_dsta & S3C2410_SDIDSTA_SDIOIRQDETECT) {
if (mci_imsk & S3C2410_SDIIMSK_SDIOIRQ) {
mci_dclear = S3C2410_SDIDSTA_SDIOIRQDETECT;
writel(mci_dclear, host->base + S3C2410_SDIDSTA);
mmc_signal_sdio_irq(host->mmc);
return IRQ_HANDLED;
}
}
spin_lock_irqsave(&host->complete_lock, iflags);
mci_csta = readl(host->base + S3C2410_SDICMDSTAT);
mci_dcnt = readl(host->base + S3C2410_SDIDCNT);
mci_fsta = readl(host->base + S3C2410_SDIFSTA);
mci_dclear = 0;
if ((host->complete_what == COMPLETION_NONE) ||
(host->complete_what == COMPLETION_FINALIZE)) {
host->status = "nothing to complete";
clear_imask(host);
goto irq_out;
}
if (!host->mrq) {
host->status = "no active mrq";
clear_imask(host);
goto irq_out;
}
cmd = host->cmd_is_stop ? host->mrq->stop : host->mrq->cmd;
if (!cmd) {
host->status = "no active cmd";
clear_imask(host);
goto irq_out;
}
if (!s3cmci_host_usedma(host)) {
if ((host->pio_active == XFER_WRITE) &&
(mci_fsta & S3C2410_SDIFSTA_TFDET)) {
disable_imask(host, S3C2410_SDIIMSK_TXFIFOHALF);
tasklet_schedule(&host->pio_tasklet);
host->status = "pio tx";
}
if ((host->pio_active == XFER_READ) &&
(mci_fsta & S3C2410_SDIFSTA_RFDET)) {
disable_imask(host,
S3C2410_SDIIMSK_RXFIFOHALF |
S3C2410_SDIIMSK_RXFIFOLAST);
tasklet_schedule(&host->pio_tasklet);
host->status = "pio rx";
}
}
if (mci_csta & S3C2410_SDICMDSTAT_CMDTIMEOUT) {
dbg(host, dbg_err, "CMDSTAT: error CMDTIMEOUT\n");
cmd->error = -ETIMEDOUT;
host->status = "error: command timeout";
goto fail_transfer;
}
if (mci_csta & S3C2410_SDICMDSTAT_CMDSENT) {
if (host->complete_what == COMPLETION_CMDSENT) {
host->status = "ok: command sent";
goto close_transfer;
}
mci_cclear |= S3C2410_SDICMDSTAT_CMDSENT;
}
if (mci_csta & S3C2410_SDICMDSTAT_CRCFAIL) {
if (cmd->flags & MMC_RSP_CRC) {
if (host->mrq->cmd->flags & MMC_RSP_136) {
dbg(host, dbg_irq,
"fixup: ignore CRC fail with long rsp\n");
} else {
/* note, we used to fail the transfer
* here, but it seems that this is just
* the hardware getting it wrong.
*
* cmd->error = -EILSEQ;
* host->status = "error: bad command crc";
* goto fail_transfer;
*/
}
}
mci_cclear |= S3C2410_SDICMDSTAT_CRCFAIL;
}
if (mci_csta & S3C2410_SDICMDSTAT_RSPFIN) {
if (host->complete_what == COMPLETION_RSPFIN) {
host->status = "ok: command response received";
goto close_transfer;
}
if (host->complete_what == COMPLETION_XFERFINISH_RSPFIN)
host->complete_what = COMPLETION_XFERFINISH;
mci_cclear |= S3C2410_SDICMDSTAT_RSPFIN;
}
/* errors handled after this point are only relevant
when a data transfer is in progress */
if (!cmd->data)
goto clear_status_bits;
/* Check for FIFO failure */
if (host->is2440) {
if (mci_fsta & S3C2440_SDIFSTA_FIFOFAIL) {
dbg(host, dbg_err, "FIFO failure\n");
host->mrq->data->error = -EILSEQ;
host->status = "error: 2440 fifo failure";
goto fail_transfer;
}
} else {
if (mci_dsta & S3C2410_SDIDSTA_FIFOFAIL) {
dbg(host, dbg_err, "FIFO failure\n");
cmd->data->error = -EILSEQ;
host->status = "error: fifo failure";
goto fail_transfer;
}
}
if (mci_dsta & S3C2410_SDIDSTA_RXCRCFAIL) {
dbg(host, dbg_err, "bad data crc (outgoing)\n");
cmd->data->error = -EILSEQ;
host->status = "error: bad data crc (outgoing)";
goto fail_transfer;
}
if (mci_dsta & S3C2410_SDIDSTA_CRCFAIL) {
dbg(host, dbg_err, "bad data crc (incoming)\n");
cmd->data->error = -EILSEQ;
host->status = "error: bad data crc (incoming)";
goto fail_transfer;
}
if (mci_dsta & S3C2410_SDIDSTA_DATATIMEOUT) {
dbg(host, dbg_err, "data timeout\n");
cmd->data->error = -ETIMEDOUT;
host->status = "error: data timeout";
goto fail_transfer;
}
if (mci_dsta & S3C2410_SDIDSTA_XFERFINISH) {
if (host->complete_what == COMPLETION_XFERFINISH) {
host->status = "ok: data transfer completed";
goto close_transfer;
}
if (host->complete_what == COMPLETION_XFERFINISH_RSPFIN)
host->complete_what = COMPLETION_RSPFIN;
mci_dclear |= S3C2410_SDIDSTA_XFERFINISH;
}
clear_status_bits:
writel(mci_cclear, host->base + S3C2410_SDICMDSTAT);
writel(mci_dclear, host->base + S3C2410_SDIDSTA);
goto irq_out;
fail_transfer:
host->pio_active = XFER_NONE;
close_transfer:
host->complete_what = COMPLETION_FINALIZE;
clear_imask(host);
tasklet_schedule(&host->pio_tasklet);
goto irq_out;
irq_out:
dbg(host, dbg_irq,
"csta:0x%08x dsta:0x%08x fsta:0x%08x dcnt:0x%08x status:%s.\n",
mci_csta, mci_dsta, mci_fsta, mci_dcnt, host->status);
spin_unlock_irqrestore(&host->complete_lock, iflags);
return IRQ_HANDLED;
}
當調用(*request),即host->ops->request(host, mrq),即上文中的s3cmci_request()後,控制器與SD卡之間開始進行一次指令或數據傳輸,通信完畢後,主控芯片將產生一個內部中斷,以告知此次指令或數據傳輸完畢。這個中斷的具體值將保存在一個名為MMC_I_REG的中斷寄存器中以供讀取,中斷寄存器MMC_I_REG中相關描述如下

如果中斷寄存器MMC_I_REG中的第0位有值,則意味著數據傳輸完成,執行s3cmci_cmd_done(host, stat);
如果中斷寄存器MMC_I_REG中的第2位有值,則意味著指令傳輸完成,執行s3cmci_data_done(host, stat);
其中stat是從狀態寄存器MMC_STAT中讀取的值,在代碼裡主要起到處理錯誤狀態的作用。
-> s3cmci_cmd_done 收到結束指令的內部中斷信號,主控制器從SD卡那裡得到response,結束這次指令傳輸
這裡需要注意,寄存器MMC_RES裡已經存放了來自SD卡發送過來的response,以供讀取。
2、探測引腳引起的中斷

[cpp] view
plain copy





host->irq_cd = gpio_to_irq(host->pdata->gpio_detect);
if (host->irq_cd >= 0) {
if (request_irq(host->irq_cd, s3cmci_irq_cd,
IRQF_TRIGGER_RISING |
IRQF_TRIGGER_FALLING,
DRIVER_NAME, host)) {
dev_err(&pdev->dev,
"can't get card detect irq.\n");
ret = -ENOENT;
goto probe_free_gpio_cd;
}
} else {
dev_warn(&pdev->dev,
"host detect has no irq available\n");
gpio_direction_input(host->pdata->gpio_detect);
}
其中,irq_cd是通過GPIO轉換得到的中斷號,s3cmci_irq_cd便是該中斷實現的函數
[cpp] view
plain copy





static irqreturn_t s3cmci_irq_cd(int irq, void *dev_id)
{
struct s3cmci_host *host = (struct s3cmci_host *)dev_id;
dbg(host, dbg_irq, "card detect\n");
mmc_detect_change(host->mmc, msecs_to_jiffies(500));
return IRQ_HANDLED;
}
之前已經提到過mmc_detect_change,它將最終調用queue_delayed_work執行工作隊列裡的mmc_rescan函數。
當有SD卡插入或拔出時,硬件主控制器芯片的探測pin腳產生外部中斷,進入中斷處理函數,執行工作隊列裡的mmc_rescan,掃描SD總線,對插入或拔出SD卡作相應的處理。下文協議層將討論mmc_rescan()。
Copyright © Linux教程網 All Rights Reserved