據個人所知,Linux下SPI一直是處於被“忽略”的角色,市場上大部分板子在板級文件裏面都沒有關於SPI的相關代碼(例如,mini2440),而大部分講解驅動的的書籍也沒有專門的一章來講述關於Linux下SPI方面的內容(例如,宋寶華的Linux設備驅動開發詳解)。與I2C相比,SPI就是一個不被重視的“傢伙”,爲什麼?我也不甚瞭解。由於項目需要在UT4412BV01上移植SPI,查閱網絡上幾乎所有的SPI相關資料,都是對S3C2440和S3C6410的SPI驅動分析,而EXYNOS4412卻隻字不提,但仔細一想它們彼此之間是相通的,遂研究一番記於此,以便日後查閱之便。
1. SPI子系統架構詳解
SPI總線上有兩類設備:一類是主控端,通常作爲SOC系統的一個子模塊出現,比如很多嵌入式MPU中都常常包含SPI模塊;一類是受控端,例如一些SPI接口的Flash、傳感器等等。主控端是SPI總線的控制者,通過使用SPI協議主動發起SPI總線上的會話,而受控端則被動接受SPI主控端的指令,並作出相應的響應。
圖還未畫好,之後補上,補上後在一些寫分析,哈哈。。。
2. 重要數據結構
(1)spi_master
struct spi_master用來描述一個SPI主控制器,我們一般不需要自己編寫spi控制器驅動。
/**
* 結構體master代表一個SPI接口,或者叫一個SPI主機控制器,
* 一個接口對應一條SPI總線,master->bus_num則記錄了這個總線號。
*/
struct spi_master {
struct device dev;
struct list_head list;
/**
* 總線編號,從零開始。
* 系統會用這個值去和系統中board_list鏈表中加入的每一個boardinfo結構中的每一個spi_board_info中的bus_num進行匹配,
* (每個boardinfo結構都是一個spi_board_info的集合,每一個spi_board_info都是對應一個SPI(從)設備的描述)
* 如果匹配上就說明這個spi_board_info描述的SPI(從)設備是鏈接在此總線上的,因此就會調用spi_new_device去創建一個spi_device。
*/
s16 bus_num;
/* 支持的片選的數量。從設備的片選號不能大於這個數.該值當然不能爲0,否則會註冊失敗 */
u16 num_chipselect;
/* some SPI controllers pose alignment requirements on DMAable
* buffers; let protocol drivers know about these requirements.
*/
u16 dma_alignment;
/* spi_device.mode flags understood by this controller driver */
u16 mode_bits;
/* other constraints relevant to this driver */
u16 flags;
#define SPI_MASTER_HALF_DUPLEX BIT(0) /* can't do full duplex */
#define SPI_MASTER_NO_RX BIT(1) /* can't do buffer read */
#define SPI_MASTER_NO_TX BIT(2) /* can't do buffer write */
/* lock and mutex for SPI bus locking */
spinlock_t bus_lock_spinlock;
struct mutex bus_lock_mutex;
/* flag indicating that the SPI bus is locked for exclusive use */
bool bus_lock_flag;
int (*setup)(struct spi_device *spi); //根據spi設備更新硬件配置
/**
* 添加消息到隊列的方法。此函數不可睡眠,其作用只是安排需要的傳送,
* 並且在適當的時候(傳送完成或者失敗)調用spi_message中的complete方法,來將結果報告給用戶。
*/
int (*transfer)(struct spi_device *spi, struct spi_message *mesg);
/*cleanup函數會在spidev_release函數中被調用,spidev_release被登記爲spidev的release函數*/
void (*cleanup)(struct spi_device *spi);
};
spi控制器的驅動在kernel3.0.15/arch/arm/mach-exynos/mach-smdk4x12.c中聲明和註冊一個平臺設備,然後在kernel3.0.15/driver/spi下面建立一個平臺驅動。spi_master註冊過程中會掃描kernel3.0.15/arch/arm/mach-exynos/mach-smdk4x12.c中調用spi_register_board_info註冊的信息,爲每一個與本總線編號相同的信息建立一個spi_device。根據Linux內核的驅動模型,註冊在同一總線下的驅動和設備會進行匹配。spi_bus_type總線匹配的依據是名字,這樣當自己編寫的spi_driver和spi_device同名的時候,spi_driver的probe方法就會被用,spi_driver就能看到與自己匹配的spi_device了。
(2)spi_device
struct spi_device用來描述一個SPI從設備。
/**
* 該結構體用於描述SPI設備,也就是從設備的相關信息。
* 注意:SPI子系統只支持主模式,也就是說SOC上的SPI只能工作在master模式,
* 外圍設備只能爲slave模式。
*/
struct spi_device {
struct device dev;
struct spi_master *master; //對應的控制器指針
u32 max_speed_hz; //spi傳輸時鐘
u8 chip_select; //片選號,用來區分同一主控制器上的設備
u8 mode; //各bit的定義如下,主要是時鐘相位/時鐘極性
#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 */
#define SPI_NO_CS 0x40 /* 1 dev/bus, no chipselect */
#define SPI_READY 0x80 /* slave pulls low to pause */
u8 bits_per_word; //每個字長的比特數
int irq; //使用到的比特數
void *controller_state;
void *controller_data;
char modalias[SPI_NAME_SIZE]; //spi是設備的名字
/*
* likely need more hooks for more protocol options affecting how
* the controller talks to each chip, like:
* - memory packing (12 bit samples into low bits, others zeroed)
* - priority
* - drop chipselect after each word
* - chipselect delays
* - ...
*/
};
(3)spi_driver
struct spi_driver用於描述SPI從設備驅動。驅動核心將根據driver.name和spi_board_info的modalias進行匹配,如果modalias和name相等,則綁定驅動程序和kernel3.0.5/arch/arm/mach-exynos/mach-smdk4x12.c中調用spi_register_board_info註冊的信息對應的spi_device設備。它們的形式和struct platform_driver是一致的。
struct spi_driver {
const struct spi_device_id *id_table; //可以驅動的設備表,也就是說該驅動可以驅動一類設備
int (*probe)(struct spi_device *spi); //和spi_device匹配成功之後會調用這個方法.因此這個方法需要對設備和私有數據進行初始化
int (*remove)(struct spi_device *spi); //解除spi_device和spi_driver的綁定,釋放probe申請的資源
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;
};
(4)spi_transferstruct spi_transfer是對一次完整的數據傳輸的描述。每個spi_transfer總是讀取和寫入同樣的長度的比特數,但是可以很容易的使用空指針捨棄讀或寫,爲spi_transfer和spi_message分配的內存應該在消息處理期間保證是完整的。
struct spi_transfer {
/* it's ok if tx_buf == rx_buf (right?)
* for MicroWire, one buffer must be null
* buffers must work with dma_*map_single() calls, unless
* spi_message.is_dma_mapped reports a pre-existing mapping
*/
const void *tx_buf; //要寫入設備的數據(必須是dma_safe),或者爲NULL
void *rx_buf; //要讀取的數據緩衝(必須是dma_safe),或者爲NULL
unsigned len; //tx和tr的大小(字節數),這裏不是指它的和,而是各自的長度,它們總是相等的
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; //影響此次傳輸之後的片選,指示本次transfer結束之後是否要重新片選並調用setup改變設置
u8 bits_per_word; //每個字長的比特數,如果是0,使用默認值
u16 delay_usecs; //此次傳輸結束和片選改變之間的延時,之後就會啓動另一個傳輸或者結束整個消息
u32 speed_hz; //通信時鐘,如果是0,使用默認值
struct list_head transfer_list; //用來連接的雙向鏈表節點
};
(5)spi_message
struct spi_message就是對多個spi_transfer的封裝。在消息需要傳遞的時候,會將spi_transfer通過自己的transfer_list字段掛到spi_message的transfers鏈表頭上。spi_message用來原子的執行spi_transfer表示的一串數組傳輸請求。這個傳輸隊列是原子的,這意味着在這個消息完成之前不會有其它消息佔用總線。消息的執行總是按照FIFO的順序,向底層提交spi_message的代碼要負責管理它的內存空間。未顯示初始化的內存需要使用0來初始化。爲spi_transfer和spi_message分配的內存應該在消息處理期間保證是完整的。
struct spi_message {
struct list_head transfers; //此次消息的傳輸隊列,一個消息可以包含多個傳輸段
struct spi_device *spi; //傳輸的目的設備
unsigned is_dma_mapped:1; //如果爲真,此次調用提供dma和cpu虛擬地址
/* REVISIT: we might want a flag affecting the behavior of the
* last transfer ... allowing things like "read 16 bit length L"
* immediately followed by "read L bytes". Basically imposing
* a specific message scheduling algorithm.
*
* Some controller drivers (message-at-a-time queue processing)
* could provide that as their default scheduling algorithm. But
* others (with multi-message pipelines) could need a flag to
* tell them about such special cases.
*/
/* completion is reported through a callback */
void (*complete)(void *context); //異步調用完成後的回調函數
void *context; //回調函數的參數
unsigned actual_length; //此次傳輸的實際長度
int status; //執行的結果,成功被置0,否則是一個負的錯誤碼
/* for optional use by whatever driver currently owns the
* spi_message ... between calls to spi_async and then later
* complete(), that's the spi_master controller driver.
*/
struct list_head queue;
void *state;
};
(6)兩個重要的板級結構
兩個板級結構,其中spi_board_info用來初始化spi_device,s3c64xx_spi_info用來初始化spi_master。這兩個板級的結構需要在移植的時候在kernel3.0.15/arch/arm/mach-exynos/mach-smdk4x12.c中初始化。
spi_board_info(kernel3.0.15/linux/include/spi/spi.h)
/* 該結構也是對SPI從設備(spi_device)的描述,只不過它是板級信息,最終該結構的所有字段都將用於初始化SPI設備結構體spi_device */
struct spi_board_info {
/* the device name and module name are coupled, like platform_bus;
* "modalias" is normally the driver name.
*
* platform_data goes to spi_device.dev.platform_data,
* controller_data goes to spi_device.controller_data,
* irq is copied too
*/
char modalias[SPI_NAME_SIZE]; //spi設備名,會拷貝到spi_device的相應字段中.這是設備spi_device在SPI總線spi_bus_type上匹配驅動的唯一標識
const void *platform_data; //平臺數據
void *controller_data;
int irq; //中斷號
/* slower signaling on noisy or low voltage boards */
u32 max_speed_hz; //SPI設備工作時的波特率
/* bus_num is board specific and matches the bus_num of some
* spi_master that will probably be registered later.
*
* chip_select reflects how this chip is wired to that master;
* it's less than num_chipselect.
*/
u16 bus_num; //該SPI(從)設備所在總線的總線號,就記錄了所屬的spi_master之中的bus_num編號.一個spi_master就對應一條總線
u16 chip_select; //片選號.該SPI(從)設備在該條SPI總線上的設備號的唯一標識
/* mode becomes spi_device.mode, and is essential for chips
* where the default of SPI_CS_HIGH = 0 is wrong.
*/
u8 mode; //參考spi_device中的成員
/* ... may need additional spi_device chip config data here.
* avoid stuff protocol drivers can set; but include stuff
* needed to behave without being bound to a driver:
* - quirks like clock rate mattering when not selected
*/
};
s3c64xx_spi_info(kernel3.0.15/arch/arm/plat-samsung/include/plat/s3c64xx-spi.h)struct s3c64xx_spi_info {
int src_clk_nr;
char *src_clk_name;
bool clk_from_cmu;
int num_cs; //總線上的設備數
int (*cfg_gpio)(struct platform_device *pdev);
/* Following two fields are for future compatibility */
int fifo_lvl_mask;
int rx_lvl_offset;
int high_speed;
int tx_st_done;
};
boardinfo是用來管理spi_board_info的結構,spi_board_info在板級文件中通過spi_register_board_info(struct spi_board_info const *info, unsigned n)交由boardinfo來管理,並掛到board_list鏈表上。
boardinfo(kernel3.0.15/drivers/spi/spi.c)
struct boardinfo {
struct list_head list; //用於掛到鏈表board_list上
struct spi_board_info board_info; //存放結構體spi_board_info
};
spi_register_board_info(kernel3.0.15/drivers/spi/spi.c)
int __init
spi_register_board_info(struct spi_board_info const *info, unsigned n)
{
struct boardinfo *bi;
int i;
bi = kzalloc(n * sizeof(*bi), GFP_KERNEL);
if (!bi)
return -ENOMEM;
for (i = 0; i < n; i++, bi++, info++) {
struct spi_master *master;
memcpy(&bi->board_info, info, sizeof(*info));
mutex_lock(&board_lock);
list_add_tail(&bi->list, &board_list);
list_for_each_entry(master, &spi_master_list, list)
spi_match_master_to_boardinfo(master, &bi->board_info);
mutex_unlock(&board_lock);
}
return 0;
}