Linux驅動修煉之道-SPI驅動框架源碼分析(上)

來自:http://blog.csdn.NET/woshixingaaa/article/details/6574215


SPI協議是一種同步的串行數據連接標準,由摩托羅拉公司命名,可工作於全雙工模式。相關通訊設備可工作於m/s模式。主設備發起數據幀,允許多個從設備的存在。每個從設備

有獨立的片選信號,SPI一般來說是四線串行總線結構。
接口:
SCLK——Serial Clock(output from master)時鐘(主設備發出)
MOSI/SIMO——Master Output, Slave Input(output from master)數據信號線mosi(主設備發出)
MISO/SOMI——Master Input,Slave Outpu(output from slave)數據信號線(從設備)
SS——Slave Select(active low;output from master)片選信號

下面來看一下Linux中的SPI驅動。在linux設備驅動框架的設計中,有一個重要的主機,外設驅動框架分離的思想,如下圖。

外設a,b,c的驅動與主機控制器A,B,C的驅動不相關,主機控制器驅動不關心外設,而外設驅動也不關心主機,外設只是訪問核心層的通用的API進行數據的傳輸,主機和外設之間可以進行任意的組合。如果我們不進行如圖的主機和外設分離,外設a,b,c和主機A,B,C進行組合的時候,需要9種不同的驅動。設想一共有個主機控制器,n個外設,分離的結構是需要m+n個驅動,不分離則需要m*n個驅動。

下面介紹spi子系統的數據結構
在Linux中,使用spi_master結構來描述一個SPI主機控制器的驅動。

  1. <span style="font-size:18px;">struct spi_master {  
  2. struct device    dev;/*總線編號,從0開始*/  
  3. s16    bus_num;/*支持的片選的數量,從設備的片選號不能大於這個數量*/  
  4. u16 num_chipselect;  
  5. u16  dma_alignment;/*改變spi_device的特性如:傳輸模式,字長,時鐘頻率*/  
  6. int  (*setup)(struct spi_device *spi);/*添加消息到隊列的方法,這個函數不可睡眠,他的任務是安排發生的傳送並且調用註冊的回調函數complete()*/  
  7. int (*transfer)(struct spi_device *spi,struct spi_message *mesg);  
  8. void   (*cleanup)(struct spi_device *spi);  
  9. };</span>  

分配,註冊和註銷的SPI主機的API由SPI核心提供:

  1. struct spi_master *spi_alloc_master(struct device *host, unsigned size);  
  2. int spi_register_master(struct spi_master *master);  
  3. void spi_unregister_master(struct spi_master *master);    
在Linux中用spi_driver來描述一個SPI外設驅動。
  1. struct spi_driver {  
  2. int   (*probe)(struct spi_device *spi);  
  3. int   (*remove)(struct spi_device *spi);  
  4. void  (*shutdown)(struct spi_device *spi);  
  5. int   (*suspend)(struct spi_device *spi, pm_message_t mesg);  
  6. int   (*resume)(struct spi_device *spi);  
  7. struct device_driver  driver;  
  8. };   

可以看出,spi_driver結構體和platform_driver結構體有極大的相似性,都有probe(),remove(),suspend(),resume()這樣的接口。

Linux用spi_device來描述一個SPI外設設備。

  1. struct spi_device {  
  2. struct device        dev;  
  3. struct spi_master   *master;       //對應的控制器指針u32      
  4. max_speed_hz;  //spi通信的時鐘u8         
  5. chip_select;   //片選,用於區分同一總線上的不同設備  
  6. u8  mode;  
  7. #define    SPI_CPHA    0x01            /* clock phase */  
  8. #define    SPI_CPOL    0x02            /* clock polarity */  
  9. #define SPI_MODE_0  (0|0)           /* (original MicroWire) */#define   SPI_MODE_1  (0|SPI_CPHA)  
  10. #define SPI_MODE_2  (SPI_CPOL|0)  
  11. #define SPI_MODE_3  (SPI_CPOL|SPI_CPHA)#define  SPI_CS_HIGH 0x04            /* chipselect active high? */  
  12. #define    SPI_LSB_FIRST   0x08            /* per-word bits-on-wire */  
  13. #define  SPI_3WIRE   0x10            /* SI/SO signals shared */  
  14. #define   SPI_LOOP    0x20            /* loopback mode */  
  15. u8      bits_per_word;    //每個字長的比特數  
  16. int      irq;              //使用的中斷  
  17. void     *controller_state;  
  18. void     *controller_data;  
  19. char     modalias[32];    //名字  
  20. };    
如下圖,看這三個結構的關係,這裏spi_device與spi_master是同一個父設備,這是在spi_new_device函數中設定的,一般這個設備是一個物理設備。

這裏的spi_master_class,spi_bus_type又是什麼呢,看下邊兩個結構體:

  1. struct bus_type spi_bus_type = {     
  2.    .name       = "spi",  
  3.    .dev_attrs  = spi_dev_attrs,  
  4.    .match    = spi_match_device,  
  5.    .uevent   = spi_uevent,   
  6.    .suspend  = spi_suspend,  
  7.    .resume   = spi_resume,  
  8. };   
  9. static struct class spi_master_class = {     
  10.     .name             = "spi_master",   
  11.     .owner           = THIS_MODULE,  
  12.     .dev_release    = spi_master_release,  
  13. };    
spi_bus_type對應spi中的spi bus總線,spidev的類定義如下:

  1. static struct class *spidev_class;   
創建這個類的主要目的是使mdev/udev能在/dev下創建設備節點/dev/spiB.C。B代表總線,C代表片外設備的片選號。

下邊來看兩個板級的結構,其中spi_board_info用來初始化spi_device,s3c2410_spi_info用來初始化spi_master。這兩個板級的結構需要在移植的時候在arch/arm/mach-s3c2440/mach-smdk2440.c中初始化。

  1. struct spi_board_info {  
  2. char     modalias[32];   //設備與驅動匹配的唯一標識  
  3. const void    *platform_data;  
  4. void     *controller_data;  
  5. int        irq;  
  6. u32     max_speed_hz;  
  7. u16        bus_num;       //設備所歸屬的總線編號  
  8. u16      chip_select;  
  9. u8      mode;  
  10. };  
  11. struct s3c2410_spi_info {  
  12. int     pin_cs;         //芯片選擇管腳  
  13. unsigned int    num_cs;         //總線上的設備數  
  14. int        bus_num;        //總線號  
  15. void (*gpio_setup)(struct s3c2410_spi_info *spi, int enable);     //spi管腳配置函數  
  16. void (*set_cs)(struct s3c2410_spi_info *spi, int cs, int pol);  
  17. };    
boardinfo是用來管理spi_board_info的結構,spi_board_info通過spi_register_board_info(struct spi_board_info const *info, unsigned n)交由boardinfo來管理,並掛到board_list鏈表上,list_add_tail(&bi->list,&board_list);

  1. struct boardinfo {   
  2.  /*用於掛到鏈表頭board_list上*/  
  3. struct list_head  list;  
  4. /*管理的spi_board_info的數量*/  
  5. unsigned  n_board_info;  
  6. /*存放結構體spi_board_info*/  
  7. struct spi_board_info    board_info[0];  
  8. };   
s3c24xx_spi是S3C2440的SPI控制器在Linux內核中的具體描述,該結構包含spi_bitbang內嵌結構,控制器時鐘頻率和佔用的中斷資源等重要成員,其中spi_bitbang具體負責SPI數據的傳輸。

  1. struct s3c24xx_spi {  
  2. /* bitbang has to be first */  
  3. struct spi_bitbang  bitbang;  
  4. struct completion   done;  
  5. void __iomem      *regs;  
  6. int            irq;  
  7. int             len;  
  8. int             count;  
  9. void         (*set_cs)(struct s3c2410_spi_info *spi,  int cs, int pol);  
  10. /* data buffers */const unsigned char *tx;  
  11. unsigned char       *rx;  
  12. struct clk      *clk;  
  13. struct resource        *ioarea;  
  14. struct spi_master   *master;  
  15. struct spi_device   *curdev;  
  16. struct device       *dev;  
  17. struct s3c2410_spi_info *pdata;  
  18. };  
爲了解決多個不同的SPI設備共享SPI控制器而帶來的訪問衝突,spi_bitbang使用內核提供的工作隊列(workqueue)。workqueue是Linux內核中定義的一種回調處理方式。採用這種方式需要傳輸數據時,不直接完成數據的傳輸,而是將要傳輸的工作分裝成相應的消息(spi_message),發送給對應的workqueue,由與workqueue關聯的內核守護線程(daemon)負責具體的執行。由於workqueue會將收到的消息按時間先後順序排列,這樣就是對設備的訪問嚴格串行化,解決了衝突。

  1. <span style="font-size:18px;">struct spi_bitbang {  
  2. struct workqueue_struct *workqueue;      //工作隊列頭  
  3. struct work_struct  work;            //每一次傳輸都傳遞下來一個spi_message,都向工作隊列頭添加一個  
  4. workspinlock_t        lock;  
  5. struct list_head   queue;           //掛接spi_message,如果上一次的spi_message還沒有處理完,接下來的spi_message就掛接在queue上等待處理  
  6. u8            busy;            //忙碌標誌  
  7. u8           use_dma;  
  8. u8          flags;  
  9. struct spi_master *master;/*一下3個函數都是在函數s3c24xx_spi_probe()中被初始化*/  
  10. int  (*setup_transfer)(struct spi_device *spi,struct spi_transfer *t);   //設置傳輸模式  
  11. void    (*chipselect)(struct spi_device *spi, int is_on);                    //片選  
  12. #define    BITBANG_CS_ACTIVE   1   /* normally nCS, active low */  
  13. #define   BITBANG_CS_INACTIVE 0/*傳輸函數,由s3c24xx_spi_txrx來實現*/  
  14. int   (*txrx_bufs)(struct spi_device *spi, struct spi_transfer *t);  
  15. u32    (*txrx_word[4])(struct spi_device *spi,unsigned nsecs,u32 word, u8 bits);  
  16. };</span>  
下面來看看spi_message:

  1. struct spi_message {  
  2. struct list_head    transfers;   //此次消息的傳輸隊列,一個消息可以包含多個傳輸段  
  3. struct spi_device *spi;        //傳輸的目的設備  
  4. unsigned      is_dma_mapped:1;  //如果爲真,此次調用提供dma和cpu虛擬地址  
  5. void          (*complete)(void *context);  //異步調用完成後的回調函數  
  6. void         *context;                    //回調函數的參數  
  7. unsigned      actual_length;               //此次傳輸的實際長度  
  8. int         status;                      //執行的結果,成功被置0,否則是一個負的錯誤碼  
  9. struct list_head   queue;  
  10. void          *state;  
  11. };    
在有消息需要傳遞的時候,會將spi_transfer通過自己的transfer_list字段掛到spi_message的transfers鏈表頭上。spi_message用來原子的執行spi_transfer表示的一串數組傳輸請求。這個傳輸隊列是原子的,這意味着在這個消息完成之前不會有其他消息佔用總線。消息的執行總是按照FIFO的順序。

下面看一看spi_transfer:

  1. struct spi_transfer {  
  2. const void *tx_buf;  //要寫入設備的數據(必須是dma_safe),或者爲NULL  
  3. void       *rx_buf;  //要讀取的數據緩衝(必須是dma_safe),或者爲NULL  
  4. unsigned   len;      //tx和rx的大小(字節數),這裏不是指它的和,而是各自的長度,他們總是相等的  
  5. dma_addr_t    tx_dma;   //如果spi_message.is_dma_mapped是真,這個是tx的dma地址  
  6. dma_addr_t rx_dma;   //如果spi_message.is_dma_mapped是真,這個是rx的dma地址  
  7. unsigned   cs_change:1;    //影響此次傳輸之後的片選,指示本次tranfer結束之後是否要重新片選並調用setup改變設置,這個標誌可以較少系統開銷u8      
  8. bits_per_word;  //每個字長的比特數,如果是0,使用默認值  
  9. u16        delay_usecs;    //此次傳輸結束和片選改變之間的延時,之後就會啓動另一個傳輸或者結束整個消息  
  10. u32       speed_hz;       //通信時鐘。如果是0,使用默認值  
  11. struct list_head transfer_list; //用來連接的雙向鏈表節點  
  12. };  

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