1.1 重要的數據結構
1. spi_device
雖然用戶空間不須要直接用到spi_device結構體,可是這個結構體和用戶空間的程序有密切的關係,理解它的成員有助於理解SPI設備節點的IOCTL命令,因此首先來介紹它。
在內核中,每一個spi_device表明一個物理的SPI設備。它的成員如程序清單 1.1所示。
程序清單 1.1 spi_device
數組
struct spi_device { structdevice dev; structspi_master *master; u32 max_speed_hz; /* 通訊時鐘最大頻率 */ u8 chip_select; /* 片選號 */ u8 mode; /*SPI設備的模式,下面的宏是它各bit的含義 */ #define SPI_CPHA 0x01 /* 採樣的時鐘相位 */ #define SPI_CPOL 0x02 /* 時鐘信號起始相位:高或者是低電平*/ #define SPI_MODE_0 (0|0) #define SPI_MODE_1 (0|SPI_CPHA) #define SPI_MODE_2 (SPI_CPOL|0) #define SPI_MODE_3 (SPI_CPOL|SPI_CPHA) #define SPI_CS_HIGH 0x04 /* 爲1時片選的有效信號是高電平*/ #define SPI_LSB_FIRST 0x08 /* 發送時低比特在前 */ #define SPI_3WIRE 0x10 /* 輸入輸出信號使用同一根信號線 */ #define SPI_LOOP 0x20 /* 迴環模式 */ u8 bits_per_word; /* 每一個通訊字的字長(比特數) */ int irq; /*使用到的中斷 */ void *controller_state; void *controller_data; char modalias[32]; /* 設備驅動的名字*/ };
因爲一個SPI總線上能夠有多個SPI設備,所以須要片選號來區分它們,SPI控制器根據片選號來選擇不一樣的片選線,從而實現每次只同一個設備通訊。
spi_device的mode成員有兩個比特位含義很重要。SPI_CPHA選擇對數據線採樣的時機,0選擇每一個時鐘週期的第一個沿跳變時採樣數據,1選擇第二個時鐘沿採樣數據;SPI_CPOL選擇每一個時鐘週期開始的極性,0表示時鐘以低電平開始,1選擇高電平開始。這兩個比特有四種組合,對應SPI_MODE_0~SPI_MODE_3。
另外一個比較重要的成員是bits_per_word。這個成員指定每次讀寫的字長,單位是比特。雖然大部分SPI接口的字長是8或者16,仍然會有一些特殊的例子。須要說明的是,若是這個成員爲零的話,默認使用8做爲字長。
最後一個成員並非設備的名字,而是須要綁定的驅動的名字。數據結構
2. spi_ioc_transfer
在用戶使用設備節點的IOCTL命令傳輸數據的時候,須要用到 spi_ioc_transfer結構體,它的成員如程序清單 1.2所示。
程序清單 1.2 spi_ioc_transfer
ide
struct spi_ioc_transfer { __u64 tx_buf; /* 寫數據緩衝 */ __u64 rx_buf; /* 讀數據緩衝 */ __u32 len; /* 緩衝的長度 */ __u32 speed_hz; /* 通訊的時鐘頻率 */ __u16 delay_usecs; /* 兩個spi_ioc_transfer之間的延時 */ __u8 bits_per_word; /* 字長(比特數) */ __u8 cs_change; /* 是否改變片選 */ __u32 pad; };
每一個 spi_ioc_transfer均可以包含讀和寫的請求,其中讀和寫的長度必須相等。因此成員len不是tx_buf和rx_buf緩衝的長度之和,而是它們各自的長度。SPI控制器驅動會先將tx_buf寫到SPI總線上,而後再讀取len長度的內容到rx_buf。若是隻想進行一個方向的傳輸,把另外一個方向的緩衝置爲0就能夠了。
speed_hz和bits_per_word這兩個成員能夠爲每次通訊配置不一樣的通訊速率(必須小於spi_device的max_speed_hz)和字長,若是它們爲0的話就會使用spi_device中的配置。
delay_usecs能夠指定兩個spi_ioc_transfer之間的延時,單位是微妙。通常不用定義。
cs_change指定這個cs_change結束以後是否須要改變片選線。通常針對同一設備的連續的幾個spi_ioc_transfer,只有最後一個須要將這個成員置位。這樣省去了來回改變片選線的時間,有助於提升通訊速率。函數
1.2 得到同SPI設備通訊的設備節點
爲了在用戶空間得到和SPI設備直接通訊的設備節點,必須有兩個條件要知足:首先要有SPI控制器驅動,其次是要在內核初始化的時候註冊一個spi_board_info,它的modalias成員必須爲「spidev」。有了這兩個條件,就能夠和SPI設備進行通訊了。控制器的驅動通常由芯片廠家提供,開發者只需提供第二個條件。
spi_board_info的定義如程序清單 1.3所示。
程序清單 1.3 struct spi_board_info
ui
struct spi_board_info { char modalias[32]; /* 要綁定的驅動的名字 */ constvoid *platform_data; void *controller_data; int irq; u32 max_speed_hz; /* 通訊時鐘最大速率 */ u16 bus_num; /* 總線編號 */ u16 chip_select; /* 片選號 */ u8 mode; /* 和spi_device中的mode成員相似 */ };
要了解這個結構體各個成員的意義請參考程序清單 1.1。
定義並註冊structspi_board_info的位置通常是內核的arch/xxx/mach-xxxx/board-xxxx.c,好比3250的內核,這個文件是arch/arm/mach-lpc32xx/board-smartarm3250.c。定義並註冊struct spi_board_info的代碼如程序清單 1.4所示。
程序清單 1.4 定義並註冊spi_board_info
this
static int __init smartarm3250_spi_usp_register(void) { structspi_board_info info = { .modalias= "spidev", .max_speed_hz= 5000000, .bus_num= 0, .chip_select= 0, }; returnspi_register_board_info(&info, 1); } arch_initcall(smartarm3250_spi_usp_register);
因爲3250內核代碼在arch/arm/mach-lpc32xx/board-smartarm3250.c已經定義了一個smartarm3250_spi_eeprom_register函數,所以在增長程序清單 1.4代碼前先將這個函數註釋掉。
程序清單 1.4註冊了一個掛在0號SPI總線上的設備信息,它的片選號爲0。增長完這段代碼後將內核從新編譯。在內核啓動的時候,會爲這個設備創建一個spi_device並和0號SPI總線的驅動進行綁定。同時內核會爲這個設備申請一個主設備號爲153的的設備號,次設備號和註冊的順序有關,最多支持32個同類設備。
內核從新編譯並重啓以後,若是系統中運行了udev,/dev下就會生成一個spidevX.D設備節點,其中X是總線編號,D是片選號。對於程序清單 1.4的代碼應該自動生成的設備節點是spidev0.0。
通常SPI控制器驅動由芯片廠商提供,開發者所要在內核作的工做就是添加相似程序清單 1.4的內容。這樣內核空間的工做減小了,用戶空間的工做量加大了,由於用戶空間的開發者須要全面瞭解SPI設備的工做方式和接口協議。spa
1.3 用戶空間同設備節點的接口
對於/dev/spidevX.D設備節點,能夠進行各類操做,這一小節介紹它支持的函數接口。
1. open/close
打開和關閉設備節點沒有特別之處,直接使用open/write就能夠了。
2. read/write
讀寫SPI設備能夠直接使用read/write函數,可是每次讀或者寫的大小不能大於4096Byte。
3. IOCTL命令
用戶空間對spidev設備節點使用IOCTL命令失敗會返回-1。
l SPI_IOC_RD_MODE
讀取SPI設備對應的spi_device.mode,mode的含義請參考程序清單 1.1。使用的方法以下:
ioctl(fd,SPI_IOC_RD_MODE, &mode);
其中第三個參數是一個uint8_t類型的變量。
l SPI_IOC_WR_MODE
設置SPI設備對應的spi_device.mode。使用的方式以下:
ioctl(fd,SPI_IOC_WR_MODE, &mode);
l SPI_IOC_RD_LSB_FIRST
查看設備傳輸的時候是否先傳輸低比特位。若是是的話,返回1。使用的方式以下:
ioctl(fd,SPI_IOC_RD_LSB_FIRST, &lsb);
其中lsb是一個uint8_t類型的變量。返回的結果存在lsb中。
l SPI_IOC_WR_LSB_FIRST
設置設備傳輸的時候是否先傳輸低比特位。當傳入非零的時候,低比特在前,當傳入0的時候高比特在前(默認)。使用的方式以下:
ioctl(fd,SPI_IOC_WR_LSB_FIRST, &lsb);
l SPI_IOC_RD_BITS_PER_WORD
讀取SPI設備的字長。使用的方式以下:
ioctl(fd,SPI_IOC_RD_BITS_PER_WORD, &bits);
其中bits是一個uibt8_t類型的變量。返回的結果保存在bits中。
l SPI_IOC_WR_BITS_PER_WORD
設置SPI通訊的字長。使用的方式以下:
ioctl(fd,SPI_IOC_WR_BITS_PER_WORD, &bits);
l SPI_IOC_RD_MAX_SPEED_HZ
讀取SPI設備的通訊的最大時鐘頻率。使用的方式以下:
ioctl(fd,SPI_IOC_RD_MAX_SPEED_HZ, &speed);
其中speed是一個uint32_t類型的變量。返回的結果保存在speed中。
l SPI_IOC_WR_MAX_SPEED_HZ
設置SPI設備的通訊的最大時鐘頻率。使用的方式以下:
ioctl(fd,SPI_IOC_WR_MAX_SPEED_HZ, &speed);
l SPI_IOC_MESSAGE(N)
一次進行雙向/屢次讀寫操做。使用的方式以下:
structspi_ioc_transfer xfer[2];
......
status= ioctl(fd, SPI_IOC_MESSAGE(2), xfer);
其中N是本次通訊中xfer的數組長度。spi_ioc_transfer的信息請參考程序清單 1.2。
/************************************************************************************/
若是想要在用戶空間編寫spi驅動,這就要在內核的arch/.../mach-*/board-*.c 中聲明一個spi_board_info,
它的名字必定要是「spidev」,好比:
線程
struct spi_board_info info = { .modalias = "spidev", .max_speed_hz = 5000000, .bus_num = 0, .chip_select = 0, };
return spi_register_board_info(&info, 1);
這樣只要控制器驅動加載了,spidev模塊就會和這個設備綁定,併爲設備申請一個設備號,主設備號爲153,次設備號和設備加載的次序有關。
目前spidev支持最多32個設備。設備的名字是spidevX.D,其中X是總線編號,D是設備的片選號。若是正確安裝並配置了udev,/dev目錄下便會生成spidevX.D
設備節點。直接對這些設備節點操做就好了。
spidev的設備節點的接口包括open/close/read/write/ioctl。
~~~~~~~~~~~~~~~~~~~~~~~~~
其中open/close沒有什麼特別之處。
read/write的話有大小的限制,讀寫的大小默認不能超過4096字節。這個大小是一個模塊加載參數,能夠修改。
容許多個用戶同時打開設備節點,spidev使用mutext進行互斥,多個用戶同時讀寫時只有一個活動的用戶,其餘用戶睡眠。
spidev的ioctl命令。
~~~~~~~~
SPI_IOC_RD_MODE:讀取spi_device的mode。
SPI_IOC_RD_LSB_FIRST:若是是SPI_LSB_FIRST的方式則返回1。
SPI_IOC_RD_BITS_PER_WORD:讀取spi_device的bits_per_word.
SPI_IOC_RD_MAX_SPEED_HZ:讀取spi_device的max_speed_hz.
SPI_IOC_WR_MODE:設置spi_device的mode,並調用spi_setup當即使設置生效。
SPI_IOC_WR_LSB_FIRST:設置spi使用SPI_LSB_FIRST的傳輸模式。當即生效。
SPI_IOC_WR_BITS_PER_WORD:讀取字長。
SPI_IOC_WR_MAX_SPEED_HZ:設置時鐘速率。
不管讀取,用戶傳輸的第三個參數都被看成緩衝地址指針。讀取時存放結果,寫入時存放要寫的內容。
SPI_IOC_MESSAGE:這個命令用來進行復雜的通訊。參數涉及到一個結構體。各個成員的意義與spi_transfer一致。
指針
struct spi_ioc_transfer { __u64 tx_buf; __u64 rx_buf; __u32 len; __u32 speed_hz; __u16 delay_usecs; __u8 bits_per_word; __u8 cs_change; __u32 pad; /* If the contents of 'struct spi_ioc_transfer' ever change * incompatibly, then the ioctl number (currently 0) must change; * ioctls with constant size fields get a bit more in the way of * error checking than ones (like this) where that field varies. * * NOTE: struct layout is the same in 64bit and 32bit userspace. */ };
內核文檔中一個例子:
code
static void do_msg(int fd, int len) { struct spi_ioc_transfer xfer[2]; unsigned char buf[32], *bp; int status; memset(xfer, 0, sizeof xfer); memset(buf, 0, sizeof buf); if (len > sizeof buf) len = sizeof buf; buf[0] = 0xaa; xfer[0].tx_buf = (__u64) buf; xfer[0].len = 1; xfer[1].rx_buf = (__u64) buf; xfer[1].len = len; status = ioctl(fd, SPI_IOC_MESSAGE(2), xfer); if (status < 0) { perror("SPI_IOC_MESSAGE"); return; } printf("response(%2d, %2d): ", len, status); for (bp = buf; len; len--) printf(" %02x", *bp++); printf("/n"); }
內核在documentation/spi目錄下有spidev的例子。
注意
~~~~
雖然多個用戶不能同一時刻對spi進行設置或讀寫,可是同一用戶卻沒法組織其餘用戶修改同一設備的設置。
舉例來講,usr1打開設備節點,而後使用ioctl設置了時鐘速率,此時usr1線程被調度出去,而後usr2操做同一個設備,將它的時鐘設爲另外一個值。
此時usr1從新調度去使用read函數,則達不到預期的效果。
建議不要有兩個程序操做spidevX.D設備節點。