linux驅動開發學習筆記二十五:SPI驅動框架介紹

一、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; }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章