2012年11月15日 星期四

Linux SDIO driver programming

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">
                                                       /* 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

沒有留言:

張貼留言