============================================
作者:yuanlulu
http://blog.csdn.net/yuanlulu
著作權沒有,但是轉載請保留此段聲明
============================================
spi_master/spi_device和spi_driver的關係。
重要的資料結構:
~~~~~~~~~~
spi控制器的主題是spi_master,雖然一般不需要自己編寫spi控制器驅動,瞭解這個結構體還是必要的。
struct spi_master {<br /> struct device dev;</p><p> s16 bus_num;//匯流排編號,從零開始</p><p> u16 num_chipselect;//支援的片選的數量。從裝置的片選號不能大於這個數</p><p> /* setup mode and clock, etc (spi driver may call many times) */<br /> int (*setup)(struct spi_device *spi);//根據spi裝置更新硬體設定。 </p><p> int (*transfer)(struct spi_device *spi, struct spi_message *mesg);//添加訊息到隊列的方法。這個函數不可睡眠。它的職責是安排發生的傳送並且調用註冊的回呼函數complete()。</p><p> /* called on release() to free memory provided by spi_master */<br /> void (*cleanup)(struct spi_device *spi);//cleanup函數會在spidev_release函數中被調用,spidev_release被登記為spi dev的release函數。<br />};</p><p>
spi控制器的驅動一般在arch/.../mach-*/board-*.c 聲明,註冊一個平台裝置,然後在driver/spi下面建立一個平台驅動。
spi_master註冊過程中會掃描arch/.../mach-*/board-*.c 中調用spi_register_board_info註冊的資訊,為每一個與本匯流排編號相同的資訊建立一個spi_device。
根據Linux核心的驅動模型,註冊在同一匯流排下的驅動和裝置會進行匹配。spi_bus_type匯流排匹配的依據是名字。這樣當自己編寫的spi_driver和spi_device同名的時候,
spi_driver的probe方法就會被調用。spi_driver就能看到與自己匹配的spi_device了。
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
struct spi_driver {<br /> int (*probe)(struct spi_device *spi);//和spi匹配成功之後會調用這個方法。因此這個方法需要對裝置和私人資料進行初始化。<br /> int (*remove)(struct spi_device *spi);//解除spi_device和spi_driver的綁定,釋放probe申請的資源。<br /> void (*shutdown)(struct spi_device *spi);//關閉<br /> int (*suspend)(struct spi_device *spi, pm_message_t mesg);//掛起<br /> int (*resume)(struct spi_device *spi);//恢複<br /> struct device_driver driver;<br />};<br />
這個結構體是一個輔助性的資料結構。主要是提供驅動模型下的Binder 方法和電源管理介面。其成員driver.name是和spi_device匹配的依據。
這個結構的用處是綁定arch/.../mach-*/board-*.c 中調用spi_register_board_info註冊的資訊對應的spi_device。
核心中的一個例子如下。
static struct spi_driver tle62x0_driver = {<br /> .driver = {<br /> .name = "tle62x0",<br /> .owner = THIS_MODULE,<br /> },<br /> .probe = tle62x0_probe,<br /> .remove = __devexit_p(tle62x0_remove),<br />};</p><p>static __init int tle62x0_init(void)<br />{<br /> return spi_register_driver(&tle62x0_driver);<br />}</p><p>static __exit void tle62x0_exit(void)<br />{<br /> spi_unregister_driver(&tle62x0_driver);<br />}</p><p>
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
struct spi_device {<br /> struct device dev;<br /> struct spi_master *master;//對應的控制器指標<br /> u32 max_speed_hz;//spi通訊時鐘<br /> u8 chip_select;//片選號,用來區分同一主控制器上的裝置。<br /> u8 mode;//各位的定義如下,主要是傳輸模式、片選極性。<br />#define SPI_CPHA 0x01 /* clock phase */<br />#define SPI_CPOL 0x02 /* clock polarity */<br />#define SPI_MODE_0 (0|0) /* (original MicroWire) */<br />#define SPI_MODE_1 (0|SPI_CPHA)<br />#define SPI_MODE_2 (SPI_CPOL|0)<br />#define SPI_MODE_3 (SPI_CPOL|SPI_CPHA)<br />#define SPI_CS_HIGH 0x04 /* chipselect active high? */片選電位為高<br />#define SPI_LSB_FIRST 0x08 /* per-word bits-on-wire */先輸出低位元<br />#define SPI_3WIRE 0x10 /* SI/SO signals shared */輸入輸出共用介面,此時只能做半雙工。<br />#define SPI_LOOP 0x20 /* loopback mode */回寫/回顯模式<br /> u8 bits_per_word;//每個字長的位元數。<br /> int irq;//使用到的中斷<br /> void *controller_state;<br /> void *controller_data;<br /> char modalias[32];//名字。<br />};</p><p>~~~~~~~~~~~~~~~~~~~~~~~<br />這個結構體描述裝置的資訊。<br />struct spi_board_info {<br /> char modalias[32];//裝置名稱<br /> const void *platform_data;//平台資料<br /> void *controller_data;<br /> int irq;//中斷</p><p> /* slower signaling on noisy or low voltage boards */<br /> u32 max_speed_hz;//通訊時鐘</p><p> u16 bus_num;//匯流排號<br /> u16 chip_select;//片選號</p><p> u8 mode;//參考spi_device中的成員<br />};</p><p>
如何編寫一個spi驅動?(裝置驅動,這裡不涉及spi控制器的驅動)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
靜態聲明
======
聲明從裝置
-----------------
arch/.../mach-*/board-*.c中一般包含如下的聲明
static struct ads7846_platform_data ads_info = {<br /> .vref_delay_usecs = 100,<br /> .x_plate_ohms = 580,<br /> .y_plate_ohms = 410,<br /> };</p><p> static struct spi_board_info spi_board_info[] __initdata = {<br /> {<br /> .modalias = "ads7846",<br /> .platform_data = &ads_info,<br /> .mode = SPI_MODE_0,<br /> .irq = GPIO_IRQ(31),<br /> .max_speed_hz = 120000 /* max sample rate at 3V */ * 16,<br /> .bus_num = 1,<br /> .chip_select = 0,<br /> },<br /> };</p><p>
使用以下函數註冊上面聲明的資訊(註冊進spi子系統):
spi_register_board_info(spi_board_info, ARRAY_SIZE(spi_board_info));
這種靜態註冊,不需要登出。
編寫驅動
-------------
SPI通訊驅動和平台裝置驅動非常類似
static struct spi_driver CHIP_driver = {<br /> .driver = {<br /> .name = "CHIP",<br /> .owner = THIS_MODULE,<br /> },</p><p> .probe = CHIP_probe,<br /> .remove = __devexit_p(CHIP_remove),<br /> .suspend = CHIP_suspend,<br /> .resume = CHIP_resume,<br /> };</p><p>
驅動核心會將board_info的modalias成員為“CHIP”的裝置和本驅動進行綁定。
probe的代碼應該像下面這樣
static int __devinit CHIP_probe(struct spi_device *spi)<br /> {<br /> struct CHIP *chip;<br /> struct CHIP_platform_data *pdata;</p><p> /* assuming the driver requires board-specific data: */<br /> pdata = &spi->dev.platform_data;<br /> if (!pdata)<br /> return -ENODEV;</p><p> /* get memory for driver's per-chip state */<br /> chip = kzalloc(sizeof *chip, GFP_KERNEL);//為自己的結構體申請記憶體空間。<br /> if (!chip)<br /> return -ENOMEM;<br /> spi_set_drvdata(spi, chip);</p><p> ... etc//其他句子。<br /> return 0;<br /> }</p><p>
probe做私人資料的初始化。之後就可以使用probe得到的spi_device和裝置進行通訊了。通訊的過程根本不需要spi_driver。
spi_driver這個結構體僅僅是為了讓驅動得到相應spi_device的指標和進行電源管理;
再在spi_driver之上使用input子系統或者混雜裝置已經和spi本身無關了。
spi_device是驅動的下界,它的上界包含了sysfs、輸入子系統、ALSA、網路層、MTD、字元裝置架構或者其他Linux子系統。
非靜態聲明
======
靜態聲明是在核心編譯時間便已經能確定spi裝置了。但是有時無法在編譯核心時確定,加入裝置時誰都不想修改核心然後再重新編譯。
上文我們說到,spi通訊過程根本不需要spi_driver,因此僅僅建立我們的spi_device即可。
spi_busnum_to_master()可以根據匯流排號返回spi_master指標。原型如下:
struct spi_master *spi_busnum_to_master(u16 bus_num);
首先根據要使用的控制器編號(這個編號對應硬體的哪個控制器跟控制器驅動有關)獲得spi_master指標,然後像靜態聲明時一樣定義spi_board_info,
最後調用struct spi_device *spi_new_device(struct spi_master *master, struct spi_board_info *chip)申請並註冊裝置。返回非null 指標意味著可以使用
返回的spi_device指標進行通訊了。注意:從代碼中看到,spi_new_device並不能初始化spi_device的bits_per_word成員,因此在得到spi_device之後還因該定義這個字長。
裝置移走之後登出裝置使用spi_unregister_device()。
如何通訊
~~~~
使用封裝介面
==================
static inline int
spi_write(struct spi_device *spi, const u8 *buf, size_t len);//同步寫,可能睡眠。成功返回零,失敗返回負的值。
static inline int
spi_read(struct spi_device *spi, u8 *buf, size_t len)//同步讀,寫,可能睡眠。成功返回零,失敗返回負的值。
extern int spi_write_then_read(struct spi_device *spi, const u8 *txbuf, unsigned n_tx, u8 *rxbuf, unsigned n_rx);//可能睡眠。
先寫n_tx個位元組再讀n_rx個位元組。成功返回零,失敗返回負的值。讀和寫的位元組數不應該大於32。
static inline ssize_t spi_w8r8(struct spi_device *spi, u8 cmd)//上個函數的封裝。寫8bit然後再讀8bit。
static inline ssize_t spi_w8r16(struct spi_device *spi, u8 cmd)//寫寫8bit然後再讀16bit。
原始介面
================
static inline int
spi_async(struct spi_device *spi, struct spi_message *message);//非同步讀寫。可以在不能睡眠的上下文調用。
extern int spi_sync(struct spi_device *spi, struct spi_message *message);//同步讀寫。會睡眠,不能在中斷上下文中使用。所有同步的spi傳輸介面都是對它的封裝。
下面研究spi_message 。
spi_message
~~~~~~~
struct spi_message {<br /> struct list_head transfers;//此次訊息的傳輸段隊列。一個訊息可以包含多個傳輸段。</p><p> struct spi_device *spi;//傳輸的目的裝置</p><p> unsigned is_dma_mapped:1;//如果為真,此次調用提供dma和cpu虛擬位址。</p><p> /* completion is reported through a callback */<br /> void (*complete)(void *context);//非同步呼叫完成後的回呼函數<br /> void *context;//回呼函數的參數<br /> unsigned actual_length;//此次傳輸的實際長度<br /> int status;//執行的結果。成功被置0,否則是一個負的錯誤碼。</p><p> struct list_head queue;//下面兩個成員是給擁有本訊息的驅動選用的。spi_master會使用它們。自己最好不要使用。<br /> void *state;<br />};</p><p>
spi_message用來原子的執行spi_transfer表示的一串數組傳輸請求。
這個傳輸隊列是原子的,這意味著在這個訊息完成之前不會有其它訊息佔用匯流排。
訊息的執行總是按照FIFO的順序。
向底層提交spi_message的代碼要負責管理它的記憶體空間。未顯示初始化的記憶體需要使用0來初始化。
spi_transfer
----------------
上面看到,一個spi_message是由多個spi_transfer組成的。
每個spi_transfer總是讀取和寫入同樣長度的位元數,但是可以很容易的使用null 指標捨棄讀或寫。
為spi_transfer和spi_message分配的記憶體應該在訊息處理期間保證是完整的。
struct spi_transfer {<br /> /* it's ok if tx_buf == rx_buf (right?)<br /> * for MicroWire, one buffer must be null<br /> * buffers must work with dma_*map_single() calls, unless<br /> * spi_message.is_dma_mapped reports a pre-existing mapping<br /> */<br /> const void *tx_buf;//要寫入裝置的資料(必須是dma_safe),或者為NULL.<br /> void *rx_buf;//要讀取的資料緩衝(必須是dma_safe),或者為NULL.<br /> unsigned len;//tx和rx的大小(位元組數)。這裡不是指它的和,而是各自的長度,他們總是相等的。</p><p> dma_addr_t tx_dma;//如果spi_message.is_dma_mapped是真,這個是tx的dma地址<br /> dma_addr_t rx_dma;//如果spi_message.is_dma_mapped是真,這個是rx的dma地址</p><p> unsigned cs_change:1;//影響此次傳輸之後的片選。指示本次transfer結束之後是否要重新片選並調用setup改變設定。這個標誌可以減少系統開銷。<br /> u8 bits_per_word;//每個字長的位元數。如果是0,使用預設值<br /> u16 delay_usecs;//此次傳輸結束和片選改變之間的延時,之後就會啟動另一個傳輸或者結束整個訊息。<br /> u32 speed_hz;//通訊時鐘。如果是0,使用預設值</p><p> struct list_head transfer_list;//用來串連的雙向鏈表節點。<br />}; </p><p>
控制器驅動會先寫入tx的資料,然後讀取同樣長度的資料。長度指示是len。
如果tx_buff是null 指標,填充rx_buff的時候會輸出0(為了產生接收的時鐘),如果rx_buff是NULL,接收到的資料將被丟棄。
只有len長讀的資料會被輸出和接收。
輸出不完整的字長是錯誤的(比如字長為2位元組的時候輸出三個位元組,最後一個位元組湊不成一個整字)。
本地記憶體中的資料總是使用本地cpu的位元組序,無論spi的位元組序是大段模式還是小段模式(使用SPI_LSB_FIRS)
當spi_transfer的字長不是8bit的2次冪的整數倍,這些資料字就包含擴充位。在spi通訊驅動看來記憶體中的資料總是剛好
對齊的,所以rx中位定義和rx中未使用的位元位總是最高有效位。(比如13bit的字長,每個字佔2位元組,rx和tx都應該如此存放)
所有的spi傳輸都以使能相關的片選線為開始。一般來說片選線在本訊息結束之前保持有效狀態。驅動可以使用
spi_transfer中的cs_change成員來影響片選:
(i)如果transfer不是message的最後一個,這個標誌量可以方便的將片選線置位無效的狀態。
有時需要這種方法來告知晶片一個命令的結束並使晶片完成這一批處理任務。
(ii)當這個trasfer是最後一個時,片選可以一直保持有效知道下一個transfer到來。
在多spi從機的匯流排上沒有辦法阻止其他裝置接收資料,這種方法可以作為一個特別的提示;開始往另一個裝置傳輸資訊就要先將
本晶片的片選置為無效。但在其他情況下,這可以保證正確性。一些裝置後面的資訊依賴於前面的資訊並且在一個處理序列完成後需要
禁用片選線。
上面這段是翻譯的,講的不明白。
再說一下:cs_change影響此transfer完成後是否禁用片選線並調用setup改變更配置置。(這個標誌量就是chip select change片選改變的意思)
沒有特殊情況,一個spi_message因該只在最後一個transfer置位該標誌量。
spi_message的函數介面
-------------------------------
static inline void spi_message_init(struct spi_message *m);//初始化,實際是用0填充並初始化鏈表節點。
static inline void
spi_message_add_tail(struct spi_transfer *t, struct spi_message *m);//將transfer加到message的鏈表尾部。
static inline void
spi_transfer_del(struct spi_transfer *t);//將transfer從message鏈表中移除
static inline struct spi_message *spi_message_alloc(unsigned ntrans, gfp_t flags);//建立一個message並建立 ntrans個transfer加入鏈表,flag是申請記憶體時的標誌。
static inline void spi_message_free(struct spi_message *m);//釋放message。
bits_per_word
-----------------------
在spi_device和spi_transfer中均有bits_per_word的定義。
spi控制器驅動中會首先使用spi_transfer中的定義,如果它的值為0則使用預設值,也就是spi_device的bits_per_word定義,如果它也為零,則使用預設值8(bit).
在spi_board_info 中沒有定義bits_per_word,而且spi_write/spi_read等封裝介面函數都沒有初始化這個變數。因此如果字長為8不能滿足需要,應該顯式的初始化spi_device的bits_per_word,或者使用
原始介面並初始化spi_transfer的bits_per_word成員。
,