用戶空間---spi

============================================
作者:yuanlulu
http://blog.csdn.net/yuanlulu


版權沒有,但是轉載請保留此段聲明
============================================


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

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

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

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設備的工作方式和接口協議。

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.
  */
};
內核文檔中一個例子:

  1. static void do_msg(int fd, int len) 
  2. struct spi_ioc_transfer xfer[2]; 
  3. unsigned char  buf[32], *bp; 
  4. int   status; 
  5. memset(xfer, 0, sizeof xfer); 
  6. memset(buf, 0, sizeof buf); 
  7. if (len > sizeof buf) 
  8.   len = sizeof buf; 
  9. buf[0] = 0xaa; 
  10. xfer[0].tx_buf = (__u64) buf; 
  11. xfer[0].len = 1; 
  12. xfer[1].rx_buf = (__u64) buf; 
  13. xfer[1].len = len; 
  14. status = ioctl(fd, SPI_IOC_MESSAGE(2), xfer); 
  15. if (status < 0) { 
  16.   perror("SPI_IOC_MESSAGE"); 
  17.   return
  18. printf("response(%2d, %2d): ", len, status); 
  19. for (bp = buf; len; len--) 
  20.   printf(" %02x", *bp++); 
  21. printf("/n"); 

 

內核在documentation/spi目錄下有spidev的例子。

 

注意

~~~~

雖然多個用戶不能同一時刻對spi進行設置或讀寫,但是同一用戶卻無法組織其他用戶修改同一設備的設置。

舉例來說,usr1打開設備節點,然後使用ioctl設置了時鐘速率,此時usr1線程被調度出去,然後usr2操作同一個設備,將它的時鐘設爲另一個值。

此時usr1重新調度去使用read函數,則達不到預期的效果。

建議不要有兩個程序操作spidevX.D設備節點。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章