一、SPI 驅動框架簡介
和I2C一樣,SPI 也是很常用的串行通信協議,SPI 驅動框架和 I2C 很類似,都分爲主機控制器驅動和設備驅動,主機控制器也就是 SOC的 SPI 控制器接口。
1、 SPI 主機驅動
SPI 主機驅動就是 SOC 的 SPI 控制器驅動,類似 I2C 驅動裏面的適配器驅動。Linux 內核使用 spi_master 表示 SPI 主機驅動,spi_master 是個結構體,定義在 include/linux/spi/spi.h 文件中,內容如下(有縮減):
315 struct spi_master {
316 struct device dev;
317
318 struct list_head list;
......
326 s16 bus_num;
327
328 /* chipselects will be integral to many controllers; some others
329 * might use board-specific GPIOs.
330 */
331 u16 num_chipselect;
332
333 /* some SPI controllers pose alignment requirements on DMAable
334 * buffers; let protocol drivers know about these requirements.
335 */
336 u16 dma_alignment;
337
338 /* spi_device.mode flags understood by this controller driver */
339 u16 mode_bits;
340
341 /* bitmask of supported bits_per_word for transfers */
342 u32 bits_per_word_mask;
......
347 /* limits on transfer speed */
348 u32 min_speed_hz;
349 u32 max_speed_hz;
350
351 /* other constraints relevant to this driver */
352 u16 flags;
359 /* lock and mutex for SPI bus locking */
360 spinlock_t bus_lock_spinlock;
361 struct mutex bus_lock_mutex;
362
363 /* flag indicating that the SPI bus is locked for exclusive use */
364 bool bus_lock_flag;
......
372 int (*setup)(struct spi_device *spi);
373
......
393 int (*transfer)(struct spi_device *spi,
394 struct spi_message *mesg);
......
434 int (*transfer_one_message)(struct spi_master *master,
435 struct spi_message *mesg);
......
462 };
......
第 393 行,transfer 函數,和 i2c_algorithm 中的 master_xfer 函數一樣,控制器數據傳輸函數。
第 434 行,transfer_one_message 函數,也用於 SPI 數據發送,用於發送一個 spi_message, SPI 的數據會打包成 spi_message,然後以隊列方式發送出去。
也就是 SPI 主機端最終會通過 transfer 函數與 SPI 設備進行通信,因此對於 SPI 主機控制器的驅動編寫者而言 transfer 函數是需要實現的,因爲不同的 SOC 其 SPI 控制器不同,寄存器都不一樣。和 I2C 適配器驅動一樣,SPI 主機驅動一般都是 SOC 廠商去編寫的,所以我們作爲 SOC 的使用者,這一部分的驅動就不用操心了。
SPI 主機驅動的核心就是申請 spi_master,然後初始化 spi_master,最後向 Linux 內核註冊spi_master。
- spi_master 申請與釋放
spi_alloc_master 函數用於申請 spi_master,函數原型如下:
struct spi_master *spi_alloc_master(struct device *dev,
unsigned size)
函數參數和返回值含義如下:
dev:設備,一般是 platform_device 中的 dev 成員變量。
size:私有數據大小,可以通過 spi_master_get_devdata 函數獲取到這些私有數據。
返回值:申請到的 spi_master。
spi_master 的釋放通過 spi_master_put 函數來完成,當我們刪除一個 SPI 主機驅動的時候就需要釋放掉前面申請的 spi_master,spi_master_put 函數原型如下:
void spi_master_put(struct spi_master *master)
函數參數和返回值含義如下:
master:要釋放的 spi_master。
返回值:無。
- spi_master 的註冊與註銷
當 spi_master 初始化完成以後就需要將其註冊到 Linux 內核,spi_master 註冊函數爲spi_register_master,函數原型如下:
int spi_register_master(struct spi_master *master)
函數參數和返回值含義如下:
master:要註冊的 spi_master。
返回值:0,成功;負值,失敗。
有時候我們還會採用 spi_bitbang_start 這個 API 函數來完成spi_master 的註冊,spi_bitbang_start 函數內部其實也是通過調用 spi_register_master 函數來完成 spi_master 的註冊。
如果要註銷 spi_master 的話可以使用 spi_unregister_master 函數,此函數原型爲:
void spi_unregister_master(struct spi_master *master)
函數參數和返回值含義如下:
master:要註銷的 spi_master。
返回值:無。
如果使用 spi_bitbang_start 註冊 spi_master 的話就要使用 spi_bitbang_stop 來註銷掉spi_master。
2、 SPI 設備驅動
spi 設備驅動也和 i2c 設備驅動也很類似,Linux 內核使用 spi_driver 結構體來表示 spi 設備驅動,我們在編寫 SPI 設備驅動的時候需要實現 spi_driver。spi_driver 結構體定義在include/linux/spi/spi.h 文件中,結構體內容如下:
180 struct spi_driver {
181 const struct spi_device_id *id_table;
182 int (*probe)(struct spi_device *spi);
183 int (*remove)(struct spi_device *spi);
184 void (*shutdown)(struct spi_device *spi);
185 struct device_driver driver;
186 };
可以看出,spi_driver 和 i2c_driver、platform_driver 基本一樣,當 SPI 設備和驅動匹配成功以後 probe 函數就會執行。
同樣的,spi_driver 初始化完成以後需要向 Linux 內核註冊,spi_driver 註冊函數爲spi_register_driver,函數原型如下:
int spi_register_driver(struct spi_driver *sdrv)
函數參數和返回值含義如下:
sdrv:要註冊的 spi_driver。
返回值:0,註冊成功;賦值,註冊失敗。
註銷 SPI 設備驅動以後也需要註銷掉前面註冊的 spi_driver,使用 spi_unregister_driver 函數完成 spi_driver 的註銷,函數原型如下:
void spi_unregister_driver(struct spi_driver *sdrv)
函數參數和返回值含義如下:
sdrv:要註銷的 spi_driver。
返回值:無。
spi_driver 註冊示例程序如下:
1 /* probe 函數 */
2 static int xxx_probe(struct spi_device *spi)
3 {
4 /* 具體函數內容 */
5 return 0;
6 }
7
8 /* remove 函數 */
9 static int xxx_remove(struct spi_device *spi)
10 {
11 /* 具體函數內容 */
12 return 0;
13 }
14 /* 傳統匹配方式 ID 列表 */
15 static const struct spi_device_id xxx_id[] = {
16 {"xxx", 0},
17 {}
18 };
19
20 /* 設備樹匹配列表 */
21 static const struct of_device_id xxx_of_match[] = {
22 { .compatible = "xxx" },
23 { /* Sentinel */ }
24 };
25
26 /* SPI 驅動結構體 */
27 static struct spi_driver xxx_driver = {
28 .probe = xxx_probe,
29 .remove = xxx_remove,
30 .driver = {
31 .owner = THIS_MODULE,
32 .name = "xxx",
33 .of_match_table = xxx_of_match,
34 },
35 .id_table = xxx_id,
36 };
37
38 /* 驅動入口函數 */
39 static int __init xxx_init(void)
40 {
41 return spi_register_driver(&xxx_driver);
42 }
43
44 /* 驅動出口函數 */
45 static void __exit xxx_exit(void)
46 {
47 spi_unregister_driver(&xxx_driver);
48 }
49
50 module_init(xxx_init);
51 module_exit(xxx_exit);
第 1~36 行,spi_driver 結構體,需要 SPI 設備驅動人員編寫,包括匹配表、probe 函數等。和 i2c_driver、platform_driver 一樣,就不詳細講解了。
第 39-42 行,在驅動入口函數中調用 spi_register_driver 來註冊 spi_driver。
第 45~48 行,在驅動出口函數中調用 spi_unregister_driver 來註銷 spi_driver。
3、SPI 設備和驅動匹配過程
SPI 設備和驅動的匹配過程是由 SPI 總線來完成的,這點和 platform、I2C 等驅動一樣,SPI總線爲 spi_bus_type,定義在 drivers/spi/spi.c 文件中,內容如下:
131 struct bus_type spi_bus_type = {
132 .name = "spi",
133 .dev_groups = spi_dev_groups,
134 .match = spi_match_device,
135 .uevent = spi_uevent,
136 };
可以看出,SPI 設備和驅動的匹配函數爲 spi_match_device,函數內容如下:
99 static int spi_match_device(struct device *dev, struct device_driver *drv)
100 {
101 const struct spi_device *spi = to_spi_device(dev);
102 const struct spi_driver *sdrv = to_spi_driver(drv);
103
104 /* Attempt an OF style match */
105 if (of_driver_match_device(dev, drv))
106 return 1;
107
108 /* Then try ACPI */
109 if (acpi_driver_match_device(dev, drv))
110 return 1;
111
112 if (sdrv->id_table)
113 return !!spi_match_id(sdrv->id_table, spi);
114
115 return strcmp(spi->modalias, drv->name) == 0;
116 }
spi_match_device 函數和 i2c_match_device 函數的對於設備和驅動的匹配過程基本一樣。
第 105 行,of_driver_match_device 函數用於完成設備樹設備和驅動匹配。比較 SPI 設備節點的 compatible 屬性和 of_device_id 中的 compatible 屬性是否相等,如果相當的話就表示 SPI 設備和驅動匹配。
第 109 行,acpi_driver_match_device 函數用於 ACPI 形式的匹配。
第 113 行,spi_match_id 函數用於傳統的、無設備樹的 SPI 設備和驅動匹配過程。比較 SPI設備名字和 spi_device_id 的 name 字段是否相等,相等的話就說明 SPI 設備和驅動匹配。
第 115 行,比較 spi_device 中 modalias 成員變量和 device_driver 中的 name 成員變量是否相等。
二、SPI 設備驅動編寫流程
1、SPI 設備信息描述
採用設備樹的情況下,SPI 設備信息描述就通過創建相應的設備子節點來完成,如下所示:
308 &ecspi1 {
309 fsl,spi-num-chipselects = <1>;
310 cs-gpios = <&gpio4 9 0>;
311 pinctrl-names = "default";
312 pinctrl-0 = <&pinctrl_ecspi1>;
313 status = "okay";
314
315 flash: m25p80@0 {
316 #address-cells = <1>;
317 #size-cells = <1>;
318 compatible = "st,m25p32";
319 spi-max-frequency = <20000000>;
320 reg = <0>;
321 };
322 };
第 309 行,設置“fsl,spi-num-chipselects”屬性爲 1,表示只有一個設備。
第 310 行,設置“cs-gpios”屬性,也就是片選信號爲 GPIO4_IO09。 第 311 行,設置“pinctrl-names”屬性,也就是 SPI 設備所使用的 IO 名字。
第 312 行,設置“pinctrl-0”屬性,也就是所使用的 IO 對應的 pinctrl 節點。
第 313 行,將 ecspi1 節點的“status”屬性改爲“okay”。
第 315~320 行,ecspi1 下的 m25p80 設備信息,每一個 SPI 設備都採用一個子節點來描述其設備信息。第 315 行的“m25p80@0”後面的“0”表示 m25p80 的接到了 ECSPI 的通道 0上。這個要根據自己的具體硬件來設置。
第 318 行,SPI 設備的 compatible 屬性值,用於匹配設備驅動。
第 319 行,“spi-max-frequency”屬性設置 SPI 控制器的最高頻率,這個要根據所使用的
SPI 設備來設置,比如在這裏將 SPI 控制器最高頻率設置爲 20MHz。
第 320 行,reg 屬性設置 m25p80 這個設備所使用的 ECSPI 通道,和“m25p80@0”後面的“0”一樣。
2、SPI 設備數據收發處理流程
當我們向 Linux 內核註冊成功 spi_driver 以後就可以使用 SPI 核心層提供的 API 函數來對設備進行讀寫操作了。首先是 spi_transfer 結構體,此結構體用於描述 SPI 傳輸信息,結構體內容如下:
603 struct spi_transfer {
604 /* it's ok if tx_buf == rx_buf (right?)
605 * for MicroWire, one buffer must be null
606 * buffers must work with dma_*map_single() calls, unless
607 * spi_message.is_dma_mapped reports a pre-existing mapping
608 */
609 const void *tx_buf;
610 void *rx_buf;
611 unsigned len;
612
613 dma_addr_t tx_dma;
614 dma_addr_t rx_dma;
615 struct sg_table tx_sg;
616 struct sg_table rx_sg;
617
618 unsigned cs_change:1;
619 unsigned tx_nbits:3;
620 unsigned rx_nbits:3;
621 #define SPI_NBITS_SINGLE 0x01 /* 1bit transfer */
622 #define SPI_NBITS_DUAL 0x02 /* 2bits transfer */
623 #define SPI_NBITS_QUAD 0x04 /* 4bits transfer */
624 u8 bits_per_word;
625 u16 delay_usecs;
626 u32 speed_hz;
627
628 struct list_head transfer_list;
629 };
第 609 行,tx_buf 保存着要發送的數據。
第 610 行,rx_buf 用於保存接收到的數據。
第 611 行,len 是要進行傳輸的數據長度,SPI 是全雙工通信,因此在一次通信中發送和接收的字節數都是一樣的,所以 spi_transfer 中也就沒有發送長度和接收長度之分。
spi_transfer 需要組織成 spi_message,spi_message 也是一個結構體,內容如下:
660 struct spi_message {
661 struct list_head transfers;
662
663 struct spi_device *spi;
664
665 unsigned is_dma_mapped:1;
......
678 /* completion is reported through a callback */
679 void (*complete)(void *context);
680 void *context;
681 unsigned frame_length;
682 unsigned actual_length;
683 int status;
684
685 /* for optional use by whatever driver currently owns the
686 * spi_message ... between calls to spi_async and then later
687 * complete(), that's the spi_master controller driver.
688 */
689 struct list_head queue;
690 void *state;
691 };
- 在使用spi_message之前需要對其進行初始化,spi_message初始化函數爲spi_message_init,函數原型如下:
void spi_message_init(struct spi_message *m)
函數參數和返回值含義如下:
m:要初始化的 spi_message。
返回值:無。
- spi_message 初始化完成以後需要將 spi_transfer 添加到spi_message 隊列中,這裏我們要用到 spi_message_add_tail 函數,此函數原型如下:
void spi_message_add_tail(struct spi_transfer *t, struct spi_message *m)
函數參數和返回值含義如下:
t:要添加到隊列中的 spi_transfer。
m:spi_transfer 要加入的 spi_message。
返回值:無。
- spi_message 準備好以後既可以進行數據傳輸了,數據傳輸分爲同步傳輸和異步傳輸,同步傳輸會阻塞的等待 SPI 數據傳輸完成,同步傳輸函數爲 spi_sync,函數原型如下:
int spi_sync(struct spi_device *spi, struct spi_message *message)
函數參數和返回值含義如下:
spi:要進行數據傳輸的 spi_device。
message:要傳輸的 spi_message。
返回值:無。
- 異步傳輸不會阻塞的等到 SPI 數據傳輸完成,異步傳輸需要設置 spi_message 中的 complete成員變量,complete 是一個回調函數,當 SPI 異步傳輸完成以後此函數就會被調用。SPI 異步傳輸函數爲 spi_async,函數原型如下:
int spi_async(struct spi_device *spi, struct spi_message *message)
函數參數和返回值含義如下:
spi:要進行數據傳輸的 spi_device。
message:要傳輸的 spi_message。
返回值:無。
綜上所述,SPI 數據傳輸步驟如下:
①、申請並初始化 spi_transfer,設置 spi_transfer 的 tx_buf 成員變量,tx_buf 爲要發送的數據。然後設置 rx_buf 成員變量,rx_buf 保存着接收到的數據。最後設置 len 成員變量,也就是要進行數據通信的長度。
②、使用 spi_message_init 函數初始化 spi_message。
③、使用spi_message_add_tail函數將前面設置好的spi_transfer添加到spi_message隊列中。
④、使用 spi_sync 函數完成 SPI 數據同步傳輸。
通過 SPI 進行 n 個字節的數據發送和接收的示例代碼如下所示:
/* SPI 多字節發送 */
static int spi_send(struct spi_device *spi, u8 *buf, int len) {
int ret;
struct spi_message m;
struct spi_transfer t = {
.tx_buf = buf,
.len = len,
};
spi_message_init(&m); /* 初始化 spi_message */
spi_message_add_tail(t, &m);/* 將 spi_transfer 添加到 spi_message 隊列 */
ret = spi_sync(spi, &m); /* 同步傳輸 */
return ret; }
/* SPI 多字節接收 */
static int spi_receive(struct spi_device *spi, u8 *buf, int len) {
int ret;
struct spi_message m;
struct spi_transfer t = {
.rx_buf = buf,
.len = len,
};
spi_message_init(&m); /* 初始化 spi_message */
spi_message_add_tail(t, &m);/* 將 spi_transfer 添加到 spi_message 隊列 */
ret = spi_sync(spi, &m); /* 同步傳輸 */
return ret; }