來自: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主機控制器的驅動。
- <span style="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);
- };</span>
分配,註冊和註銷的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);
- 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_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,
- };
- static struct class *spidev_class;
下邊來看兩個板級的結構,其中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;
- void *controller_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);
- };
- 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];
- };
- 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;
- };
- <span style="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);
- };</span>
- 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:
- 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; //用來連接的雙向鏈表節點
- };