通過上一篇文章的介紹,我們知道,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位。