天天看點

SPI驅動架構源碼分析(上)

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裝置驅動架構的設計中,有一個重要的主機,外設驅動架構分離的思想,如下圖。

SPI驅動架構源碼分析(上)

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

下面介紹spi子系統的資料結構:

在Linux中,使用spi_master結構來描述一個SPI主機控制器的驅動。

Cpp代碼 

"font-size: 18px;">struct spi_master {  

struct device    dev;/*總線編号,從0開始*/  

s16    bus_num;/*支援的片選的數量,從裝置的片選号不能大于這個數量*/  

u16 num_chipselect;  

u16  dma_alignment;/*改變spi_device的特性如:傳輸模式,字長,時鐘頻率*/  

int  (*setup)(struct spi_device *spi);/*添加消息到隊列的方法,這個函數不可睡眠,他的任務是安排發生的傳送并且調用注冊的回調函數complete()*/  

int (*transfer)(struct spi_device *spi,struct spi_message *mesg);  

void   (*cleanup)(struct spi_device *spi);  

};  

配置設定,注冊和登出的SPI主機的API由SPI核心提供:

struct spi_master *spi_alloc_master(struct device *host, unsigned size);  

int spi_register_master(struct spi_master *master);  

void spi_unregister_master(struct spi_master *master);    

在Linux中用spi_driver來描述一個SPI外設驅動。

struct spi_driver {  

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;  

};   

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

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

struct spi_device {  

struct device        dev;  

struct spi_master   *master;       //對應的控制器指針u32      

max_speed_hz;  //spi通信的時鐘u8         

chip_select;   //片選,用于區分同一總線上的不同裝置  

u8  mode;  

#define    SPI_CPHA    0x01            /* clock phase */  

#define    SPI_CPOL    0x02            /* clock polarity */  

#define SPI_MODE_0  (0|0)           /* (original MicroWire) */#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            /* chipselect active high? */  

#define    SPI_LSB_FIRST   0x08            /* per-word bits-on-wire */  

#define  SPI_3WIRE   0x10            /* SI/SO signals shared */  

#define   SPI_LOOP    0x20            /* loopback mode */  

u8      bits_per_word;    //每個字長的比特數  

int      irq;              //使用的中斷  

void     *controller_state;  

void     *controller_data;  

char     modalias[32];    //名字  

};    

如下圖,看這三個結構的關系,這裡spi_device與spi_master是同一個父裝置,這是在spi_new_device函數中設定的,一般這個裝置是一個實體裝置。

SPI驅動架構源碼分析(上)

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

struct bus_type spi_bus_type = {     

   .name       = "spi",  

   .dev_attrs  = spi_dev_attrs,  

   .match    = spi_match_device,  

   .uevent   = spi_uevent,   

   .suspend  = spi_suspend,  

   .resume   = spi_resume,  

static struct class spi_master_class = {     

    .name             = "spi_master",   

    .owner           = THIS_MODULE,  

    .dev_release    = spi_master_release,  

spi_bus_type對應spi中的spi bus總線,spidev的類定義如下:

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中初始化。

struct spi_board_info {  

char     modalias[32];   //裝置與驅動比對的唯一辨別  

const void    *platform_data;  

int        irq;  

u32     max_speed_hz;  

u16        bus_num;       //裝置所歸屬的總線編号  

u16      chip_select;  

u8      mode;  

struct s3c2410_spi_info {  

int     pin_cs;         //晶片選擇管腳  

unsigned int    num_cs;         //總線上的裝置數  

int        bus_num;        //總線号  

void (*gpio_setup)(struct s3c2410_spi_info *spi, int enable);     //spi管腳配置函數  

void (*set_cs)(struct s3c2410_spi_info *spi, int cs, int pol);  

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);

struct boardinfo {   

 /*用于挂到連結清單頭board_list上*/  

struct list_head  list;  

/*管理的spi_board_info的數量*/  

unsigned  n_board_info;  

/*存放結構體spi_board_info*/  

struct spi_board_info    board_info[0];  

s3c24xx_spi是S3C2440的SPI控制器在Linux核心中的具體描述,該結構包含spi_bitbang内嵌結構,控制器時鐘頻率和占用的中斷資源等重要成員,其中spi_bitbang具體負責SPI資料的傳輸。

struct s3c24xx_spi {  

/* bitbang has to be first */  

struct spi_bitbang  bitbang;  

struct completion   done;  

void __iomem      *regs;  

int            irq;  

int             len;  

int             count;  

void         (*set_cs)(struct s3c2410_spi_info *spi,  int cs, int pol);  

/* data buffers */const unsigned char *tx;  

unsigned char       *rx;  

struct clk      *clk;  

struct resource        *ioarea;  

struct spi_master   *master;  

struct spi_device   *curdev;  

struct device       *dev;  

struct s3c2410_spi_info *pdata;  

為了解決多個不同的SPI裝置共享SPI控制器而帶來的通路沖突,spi_bitbang使用核心提供的工作隊列(workqueue)。workqueue是Linux核心中定義的一種回調處理方式。采用這種方式需要傳輸資料時,不直接完成資料的傳輸,而是将要傳輸的工作分裝成相應的消息(spi_message),發送給對應的workqueue,由與workqueue關聯的核心守護線程(daemon)負責具體的執行。由于workqueue會将收到的消息按時間先後順序排列,這樣就是對裝置的通路嚴格串行化,解決了沖突。

"font-size: 18px;">struct spi_bitbang {  

struct workqueue_struct *workqueue;      //工作隊列頭  

struct work_struct  work;            //每一次傳輸都傳遞下來一個spi_message,都向工作隊列頭添加一個  

workspinlock_t        lock;  

struct list_head   queue;           //挂接spi_message,如果上一次的spi_message還沒有處理完,接下來的spi_message就挂接在queue上等待處理  

u8            busy;            //忙碌标志  

u8           use_dma;  

u8          flags;  

struct spi_master *master;/*一下3個函數都是在函數s3c24xx_spi_probe()中被初始化*/  

int  (*setup_transfer)(struct spi_device *spi,struct spi_transfer *t);   //設定傳輸模式  

void    (*chipselect)(struct spi_device *spi, int is_on);                    //片選  

#define    BITBANG_CS_ACTIVE   1   /* normally nCS, active low */  

#define   BITBANG_CS_INACTIVE 0/*傳輸函數,由s3c24xx_spi_txrx來實作*/  

int   (*txrx_bufs)(struct spi_device *spi, struct spi_transfer *t);  

u32    (*txrx_word[4])(struct spi_device *spi,unsigned nsecs,u32 word, u8 bits);  

下面來看看spi_message:

struct spi_message {  

struct list_head    transfers;   //此次消息的傳輸隊列,一個消息可以包含多個傳輸段  

struct spi_device *spi;        //傳輸的目的裝置  

unsigned      is_dma_mapped:1;  //如果為真,此次調用提供dma和cpu虛拟位址  

void          (*complete)(void *context);  //異步調用完成後的回調函數  

void         *context;                    //回調函數的參數  

unsigned      actual_length;               //此次傳輸的實際長度  

int         status;                      //執行的結果,成功被置0,否則是一個負的錯誤碼  

struct list_head   queue;  

void          *state;  

在有消息需要傳遞的時候,會将spi_transfer通過自己的transfer_list字段挂到spi_message的transfers連結清單頭上。spi_message用來原子的執行spi_transfer表示的一串數組傳輸請求。這個傳輸隊列是原子的,這意味着在這個消息完成之前不會有其他消息占用總線。消息的執行總是按照FIFO的順序。

下面看一看spi_transfer:

struct spi_transfer {  

const void *tx_buf;  //要寫入裝置的資料(必須是dma_safe),或者為NULL  

void       *rx_buf;  //要讀取的資料緩沖(必須是dma_safe),或者為NULL  

unsigned   len;      //tx和rx的大小(位元組數),這裡不是指它的和,而是各自的長度,他們總是相等的  

dma_addr_t    tx_dma;   //如果spi_message.is_dma_mapped是真,這個是tx的dma位址  

dma_addr_t rx_dma;   //如果spi_message.is_dma_mapped是真,這個是rx的dma位址  

unsigned   cs_change:1;    //影響此次傳輸之後的片選,訓示本次tranfer結束之後是否要重新片選并調用setup改變設定,這個标志可以較少系統開銷u8      

bits_per_word;  //每個字長的比特數,如果是0,使用預設值  

u16        delay_usecs;    //此次傳輸結束和片選改變之間的延時,之後就會啟動另一個傳輸或者結束整個消息  

u32       speed_hz;       //通信時鐘。如果是0,使用預設值  

struct list_head transfer_list; //用來連接配接的雙向連結清單節點  

};  

繼續閱讀