1. 程式人生 > >Linux SPI匯流排和裝置驅動架構之二:SPI通用介面層

Linux SPI匯流排和裝置驅動架構之二:SPI通用介面層

通過上一篇文章的介紹,我們知道,SPI通用介面層用於把具體SPI裝置的協議驅動和SPI控制器驅動聯接在一起,通用介面層除了為協議驅動和控制器驅動提供一系列的標準介面API,同時還為這些介面API定義了相應的資料結構,這些資料結構一部分是SPI裝置、SPI協議驅動和SPI控制器的資料抽象,一部分是為了協助資料傳輸而定義的資料結構。另外,通用介面層還負責SPI系統與Linux裝置模型相關的初始化工作。本章的我們就通過這些資料結構和API的討論來對整個通用介面層進行深入的瞭解。

/*****************************************************************************************************/
宣告:本博內容均由

http://blog.csdn.net/droidphone原創,轉載請註明出處,謝謝!
/*****************************************************************************************************/

SPI通用介面層的程式碼集中在:/drivers/spi/spi.c中。

SPI裝置模型的初始化

通常地,根據linux裝置模型的組織方式,各種裝置會掛在合適的總線上,裝置驅動和裝置通過匯流排互相進行匹配,使得裝置能夠找到正確的驅動程式進行控制和驅動。同時,性質相似的裝置可以歸為某一個類的裝置,它們具有某些共同的裝置屬性,在裝置模型上就是所謂的class。SPI裝置也不例外,它們也遵循linux的裝置模型的規則:

struct bus_type spi_bus_type = {
        .name           = "spi",
        .dev_attrs      = spi_dev_attrs,
        .match          = spi_match_device,
        .uevent         = spi_uevent,
        .pm             = &spi_pm,
};  
    
static struct class spi_master_class = {
        .name           = "spi_master",
        .owner          = THIS_MODULE,
        .dev_release    = spi_master_release,
};

static int __init spi_init(void)
{
        int     status;

        buf = kmalloc(SPI_BUFSIZ, GFP_KERNEL);
        ......
        status = bus_register(&spi_bus_type);
        ......
        status = class_register(&spi_master_class);
        ......
        return 0;
        ......
}


postcore_initcall(spi_init);
可見,在初始化階段,spi_init函式向系統註冊了一個名為spi的匯流排型別,同時也為SPI控制器註冊了一個名為spi_master的裝置類。這樣,以後在sysfs中就會出現以下兩個檔案節點:
  • sys/bus/spi
  • sys/class/spi_master
代表spi匯流排的spi_bus_type結構的match欄位指向了spi_match_device函式,該函式用於匹配spi總線上的裝置和驅動,具體的程式碼這裡就不貼了,各位可以自行檢視核心的程式碼樹。

spi_master結構

SPI控制器負責按照設定的物理訊號格式在主控和spi裝置之間交換資料,SPI控制器資料是如何被傳輸的,而不關心資料的內容。SPI通用介面層用spi_master結構來表示一個spi控制器,我們看看它的主要欄位的意義:

欄位名稱 描述
struct device   dev spi控制器對應的device結構
struct list_head list 系統可能有多個控制器,用該連結串列連結在一個全域性連結串列變數上
s16             bus_num 該控制器對應的spi匯流排編號,從0開始,通常由板級程式碼設定
u16             num_chipselect 連線到該spi控制器的片選訊號的個數
u16             mode_bits 工作模式,由驅動解釋該模式的意義
u32             min_speed_hz 最低工作時鐘
u32             max_speed_hz 最高工作時鐘
u16             flags 用於設定某些限制條件的標誌位
int             (*setup)(struct spi_device *spi) 回撥函式,用於設定某個spi裝置在該控制器上的工作引數
int             (*transfer)(......) 回撥函式,用於把包含資料資訊的mesg結構加入控制器的訊息連結串列中
void           (*cleanup)(struct spi_device *spi) 回撥函式,當spi_master被釋放時,該函式被呼叫
struct kthread_worker   kworker 用於管理資料傳輸訊息佇列的工作佇列執行緒
struct kthread_work     pump_messages 具體實現資料傳輸佇列的工作佇列
struct list_head        queue 該控制器的訊息佇列,所有等待傳輸的訊息佇列掛在該連結串列下
struct spi_message      *cur_msg 當前正帶處理的訊息佇列
int (*prepare_transfer_hardware)(......) 回撥函式,正式發起傳送前會被呼叫,用於準備硬體資源
int (*transfer_one_message)(......) 單個訊息的原子傳送回調函式,佇列中的每個訊息都會呼叫一次該回調來完成傳輸工作
int (*unprepare_transfer_hardware)(......) 清理回撥函式
int                     *cs_gpios 片選訊號所用到的gpio

spi_master結構通常由控制器驅動定義,然後通過以下通用介面層的API註冊到系統中:

  • int spi_register_master(struct spi_master *master);

spi_device結構

SPI通用介面層用spi_device結構來表示一個spi裝置,它的各個欄位的意義如下:

struct device           dev 代表該spi裝置的device結構
struct spi_master       *master 指向該spi裝置所使用的控制器
u32     max_speed_hz 該裝置的最大工作時鐘頻率
u8      chip_select 在控制器中的片選引腳編號索引
u16     mode 裝置的工作模式,包括時鐘格式,片選訊號的有效電平等等
u8      bits_per_word 裝置每個單位資料所需要的位元數
int     irq 裝置使用的irq編號
char    modalias[SPI_NAME_SIZE] 該裝置的名字,用於spi匯流排和驅動進行配對
int     cs_gpio 片選訊號的gpio編號,通常不用我們自己設定,介面層會根據上面的chip_select欄位在spi_master結構中進行查詢並賦值

要完成向系統增加並註冊一個SPI裝置,我們還需要另一個數據結構:

struct spi_board_info {
        char            modalias[SPI_NAME_SIZE];
        const void      *platform_data;
        void            *controller_data;
        int             irq;
        u32             max_speed_hz;
        u16             bus_num;
        u16             chip_select;
        u16             mode;
};
spi_board_info結構大部分欄位和spi_device結構相對應,bus_num欄位則用來指定所屬的控制器編號,通過spi_board_info結構,我們可以有兩種方式向系統增加spi裝置。第一種方式是在SPI控制器驅動已經被載入後,我們使用通用介面層提供的如下API來完成:
  • struct spi_device *spi_new_device(struct spi_master *master, struct spi_board_info *chip);
第二種方式是在板子的初始化程式碼中,定義一個spi_board_info陣列,然後通過以下API註冊spi_board_info:
  • int spi_register_board_info(struct spi_board_info const *info, unsigned n);
上面這個API會把每個spi_board_info掛在全域性連結串列變數board_list上,並且遍歷已經在系統中註冊了的控制器,匹配上相應的控制器並取得它們的spi_master結構指標,最終也會通過spi_new_device函式新增SPI裝置。因為spi_register_board_info可以在板子的初始化程式碼中呼叫,可能這時控制器驅動尚未載入,此刻無法取得相應的spi_master指標,不過不要擔心,控制器驅動被載入時,一定會呼叫spi_register_master函式來註冊spi_master結構,而spi_register_master函式會反過來遍歷全域性連結串列board_list上的spi_board_info,然後通過spi_new_device函式新增SPI裝置。

spi_driver結構

根據linux的裝置模型,有device就必定有driver與之對應,上一節介紹的spi_device結構中內嵌了device結構欄位dev,同樣地,代表驅動程式的spi_driver結構也內嵌了device_driver結構:

struct spi_driver {
        const struct spi_device_id *id_table;
        int                     (*probe)(struct spi_device *spi);
        int                     (*remove)(struct spi_device *spi);
        void                    (*shutdown)(struct spi_device *spi);
        int                     (*suspend)(struct spi_device *spi, pm_message_t mesg);
        int                     (*resume)(struct spi_device *spi);
        struct device_driver    driver;
};
id_table欄位用於指定該驅動可以驅動的裝置名稱,匯流排的匹配函式會把id_table中指定的名字和spi_device結構中的modalias欄位進行比較,兩者相符即表示匹配成功,然後出發spi_driver的probe回撥函式被呼叫,從而完成驅動程式的初始化工作。通用介面層提供如下API來完成spi_driver的註冊:
int spi_register_driver(struct spi_driver *sdrv)
{
        sdrv->driver.bus = &spi_bus_type;
        if (sdrv->probe)
                sdrv->driver.probe = spi_drv_probe;
        if (sdrv->remove)
                sdrv->driver.remove = spi_drv_remove;
        if (sdrv->shutdown)
                sdrv->driver.shutdown = spi_drv_shutdown;
        return driver_register(&sdrv->driver);
}

需要注意的是,這裡的spi_driver結構代表的是具體的SPI協議驅動程式。

spi_message和spi_transfer結構

要完成和SPI裝置的資料傳輸工作,我們還需要另外兩個資料結構:spi_message和spi_transfer。spi_message包含了一個的spi_transfer結構序列,一旦控制器接收了一個spi_message,其中的spi_transfer應該按順序被髮送,並且不能被其它spi_message打斷,所以我們認為spi_message就是一次SPI資料交換的原子操作。下面我們看看這兩個資料結構的定義:

struct spi_message {
        struct list_head        transfers;

        struct spi_device       *spi;

        unsigned                is_dma_mapped:1;

        /* completion is reported through a callback */
        void                    (*complete)(void *context);
        void                    *context;
        unsigned                frame_length;
        unsigned                actual_length;
        int                     status;

        struct list_head        queue;
        void                    *state;
};
連結串列欄位queue用於把該結構掛在代表控制器的spi_master結構的queue欄位上,控制器上可以同時被加入多個spi_message進行排隊。另一個連結串列欄位transfers則用於連結掛在本message下的spi_tranfer結構。complete回撥函式則會在該message下的所有spi_transfer都被傳輸完成時被呼叫,以便通知協議驅動處理接收到的資料以及準備下一批需要傳送的資料。我們再來看看spi_transfer結構:
struct spi_transfer {
        const void      *tx_buf;
        void            *rx_buf;
        unsigned        len;

        dma_addr_t      tx_dma;
        dma_addr_t      rx_dma;

        unsigned        cs_change:1;
        u8              tx_nbits;
        u8              rx_nbits;
        u8              bits_per_word;
        u16             delay_usecs;
        u32             speed_hz;

        struct list_head transfer_list;
};
首先,transfer_list連結串列欄位用於把該transfer掛在一個spi_message結構中,tx_buf和rx_buf提供了非dma模式下的資料緩衝區地址,len則是需要傳輸資料的長度,tx_dma和rx_dma則給出了dma模式下的緩衝區地址。原則來講,spi_transfer才是傳輸的最小單位,之所以又引進了spi_message進行打包,我覺得原因是:有時候希望往spi裝置的多個不連續的地址(或暫存器)一次性寫入,如果沒有spi_message進行把這樣的多個spi_transfer打包,因為通常真正的資料傳送工作是在另一個核心執行緒(工作佇列)中完成的,不打包的後果就是會造成更多的程序切換,效率降低,延遲增加,尤其對於多個不連續地址的小規模資料傳送而言就更為明顯。
通用介面層為我們提供了一系列用於操作和維護spi_message和spi_transfer的API函式,這裡也列一下。

用於初始化spi_message結構:

  • void spi_message_init(struct spi_message *m);
把一個spi_transfer加入到一個spi_message中(注意,只是加入,未啟動傳輸過程),和移除一個spi_transfer:
  • void spi_message_add_tail(struct spi_transfer *t, struct spi_message *m);
  •  void spi_transfer_del(struct spi_transfer *t);
以上兩個API的組合,初始化一個spi_message並新增數個spi_transfer結構:
  • void spi_message_init_with_transfers(struct spi_message *m, struct spi_transfer *xfers, unsigned int num_xfers);
分配一個自帶數個spi_transfer機構的spi_message:
  • struct spi_message *spi_message_alloc(unsigned ntrans, gfp_t flags);
發起一個spi_message的傳送操作:
  • 非同步版本    int spi_async(struct spi_device *spi, struct spi_message *message);
  • 同步版本    int spi_sync(struct spi_device *spi, struct spi_message *message);
利用以上這些API函式,SPI裝置的協議驅動程式就可以完成與某個SPI裝置的資料交換工作,同時也可以看到,因為有通用介面層的隔離,控制器驅動對於協議驅動程式來說是透明的,也就是說,協議驅動程式只關心具體需要處理和交換的資料,無需關心控制器是如何傳送這些資料的。spi_master,spi_message,spi_transfer這幾個資料結構的關係可以用下圖來描述:



總結一下,協議驅動傳送資料的流程大致是這樣的:

  • 定義一個spi_message結構;
  • 用spi_message_init函式初始化spi_message;
  • 定義一個或數個spi_transfer結構,初始化併為資料準備緩衝區並賦值給spi_transfer相應的欄位(tx_buf,rx_buf等);
  • 通過spi_message_init函式把這些spi_transfer掛在spi_message結構下;
  • 如果使用同步方式,呼叫spi_sync(),如果使用非同步方式,呼叫spi_async();
另外,通用介面層也為一些簡單的資料傳輸提供了獨立的API來完成上述的組合過程:
  • int spi_write(struct spi_device *spi, const void *buf, size_t len);    ----    同步方式傳送資料。
  • int spi_read(struct spi_device *spi, void *buf, size_t len);    ----    同步方式接收資料。
  • int spi_sync_transfer(struct spi_device *spi, struct spi_transfer *xfers, unsigned int num_xfers);    ----   同步方式,直接傳送數個spi_transfer,接收和傳送。
  • int spi_write_then_read(struct spi_device *spi, const void *txbuf, unsigned n_tx, void *rxbuf, unsigned n_rx);    ----    先寫後讀。
  • ssize_t spi_w8r8(struct spi_device *spi, u8 cmd);    ----    寫8位,然後讀8位。
  • ssize_t spi_w8r16(struct spi_device *spi, u8 cmd);    ----    寫8位,然後讀16位。