Open Source 的 standard SDIO stack 大約在 2.6.24 的時候,由 Pierre Ossman 加入 Linux kernel。某種程度上,SDIO stack 是基於 MMC stack 發展出來的,所以蠻多功能會用到 MMC 這個詞。稍微簡介一下檔案放置的位置:
- mmc/host 包含了 host adapter drivers,其中的 sdhci 是本文主要使用的 adapter
- mmc/core 包含了 mmc subsystem code (mmc_core)
- mmc/card 包含了使用 MMC/SDIO bus 的 device drivers,sdio_uart 是一個例子
* sdhci: Secure Digital Host Controller Interface
以下節錄自 {kernel_source_tree}/drivers/mmc/card/sdio_uart.c
------------------------------------------------------------------------------------
static const struct sdio_device_id sdio_uart_ids[] = {
{ SDIO_DEVICE_CLASS(SDIO_CLASS_UART) },
{ SDIO_DEVICE_CLASS(SDIO_CLASS_GPS) },
{
/* end: all zeroes */ },
};
MODULE_DEVICE_TABLE(sdio, sdio_uart_ids);
static struct sdio_driver sdio_uart_driver = {
.probe = sdio_uart_probe,
.remove = sdio_uart_remove,
.name =
"sdio_uart",
/* 驅動程式名稱 */
.id_table = sdio_uart_ids,
};
------------------------------------------------------------------------------------
有寫過 Linux USB device driver 的人,應該會覺得 SDIO 註冊 device driver 的方式非常類似於 USB,寫起 SDIO 版本的驅動應該就比較不陌生。首先是要先填寫 struct sdio_driver 的結構體,填寫 struct sdio_driver 所要準備的有兩個 callback function 成員,一個是 probe,另一個是 remove,即探測和移除函數,它們分別在裝置插入和拔出的時候被呼叫,用於初始化和釋放軟硬體資源。而 struct sdio_driver 的 id_table 成員描述了這個 SDIO 驅動程式所支援的裝置清單,它指向一個 sdio_device_id 的陣列,你可以使用下列巨集來產生該陣列成員:
- SDIO_DEVICE( vendor, device )
- SDIO_DEVICE_CLASS( device_class )
而在填完 struct sdio_driver 之後,你可以使用下列這兩個函數來註冊和註銷 sdio_driver:
- int sdio_register_driver(struct sdio_driver *);
- void sdio_unregister_driver(struct sdio_driver *);
註冊完 sdio_driver 之後,系統便可以在裝置插拔的時候呼叫對應的 probe 和 remove 函數。
- int (*probe)(struct sdio_func *, const struct sdio_device_id *);
- void (*remove)(struct sdio_func *);
可以看到這兩個函數的第一個指標參數型別都是 struct sdio_func,定義如下:
/*
* SDIO function devices
*/
struct sdio_func {
struct mmc_card *card;
/* the card this device belongs to */
struct device dev;
/* the device */
sdio_irq_handler_t *irq_handler;
/* IRQ callback */
unsigned int num;
/* function number */
unsigned char class;
/* standard interface class */
unsigned short vendor;
/* vendor id */
unsigned short device;
/* device id */
unsigned max_blksize;
/* maximum block size */
unsigned cur_blksize;
/* current block size */
unsigned enable_timeout;
/* max enable timeout in msec */
unsigned int state;
/* function state */
#define SDIO_STATE_PRESENT (1<<0 color="#0000ff" font="font" nbsp="nbsp">0>
/* present in sysfs */
u8 tmpbuf[4];
/* DMA:able scratch buffer */
unsigned num_info;
/* number of info strings */
const char **info;
/* info strings */
struct sdio_func_tuple *tuples;
};
這個參數應是 kernel 取得 SDIO device 資訊並填好之後傳過來的,之所以要講到這個參數,是因為 driver code 中會利用到下列巨集設定/取得驅動程式私有的資料結構。
- sdio_get_drvdata(struct sdio_func*)
- sdio_set_drvdata(struct sdio_func*, void *data)
假設驅動程式私有資料結構為 struct foo_priv 且其內嵌 struct sdio_func,舉例如下:
static int
foo_probe( struct sdio_func *func, const struct sdio_device_id *id )
{
struct foo_priv *priv = kmalloc( sizeof(struct foo_priv), GFP_KERNEL );
priv->func = func;
sdio_set_drvdata(func, priv);
}
static void
foo_remove( struct sdio_func *func )
{
struct foo_priv *priv = sdio_get_drvdata(func);
priv->func = NULL;
}
接下來,在與 SDIO device 做 IO 之前,需要在 start 函數中 enable device:
- sdio_claim_host(priv->func); //取得 MMC host controller 的物理使用權
- ret = sdio_enable_func(priv->func); //Enable SDIO function
- ret = sdio_claim_irq(priv->func, foo_irq); //註冊 IRQ handler
- sdio_release_host(priv->func); //歸還 MMC host controller 的物理使用權
同樣地,做完 IO 之後,需要再 stop 函數之中 disable device:
- sdio_claim_host(priv->func); //取得 MMC host controller 的物理使用權
- sdio_release_irq(priv->func); //Disable SDIO function
- sdio_disable_func(priv->func); //註銷 IRQ handler
- sdio_release_host(priv->func); //歸還 MMC host controller 的物理使用權
另外,IO 動作的前後也需要呼叫 sdio_claim_host() 以及 sdio_release_host(),以便完成 MMC host controller 物理使用權的管理,即其取得與歸還。
一些 SDIO 的 Read/Write function 列表如下:
- 1: sdio_readb
- 2: sdio_readw
- 4: sdio_readl
- 1: sdio_writeb
- 2: sdio_writew
- 4: sdio_writel
以上的 SDIO R/W functions 都是基於 mmc_io_rw_direct() 發展出來的。
最後,一個完整實作的例子可以參考
{kernel_source_tree}/drivers/mmc/card/sdio_uart.c
作者 Nicolas Pitre 是利用 SDIO interface 實作一個 UART driver,所以對上層 USER space 的介面是 TTY driver,可是底層 IO 的部分利用到 SDIO 提供的 Read/Write API 做讀寫。
[Reference]
Linux kernel source
http://www.varsanofiev.com/inside/WritingLinuxSDIODrivers.htm
http://blog.csdn.net/gangyanliang/article/details/6873578