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主機控制器的驅動。
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_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; //用來連接配接的雙向連結清單節點
};