SPI硬件基礎(chǔ)
1、SPI hardware
SPI:Serial Perripheral Interface,串行外圍設(shè)備接口,由 Motorola 公司提出,是一種高速、全雙工、同步通信總線。SPI 以主從方式工作,通常是有一個(gè)主設(shè)備和一個(gè)或多個(gè)從設(shè)備,無應(yīng)答機(jī)制。
本文我們講解標(biāo)準(zhǔn)的 4 線 SPI,四根線如下:
①、CS/SS,Slave Select/Chip Select,片選信號(hào)線,用于選擇需要進(jìn)行通信的從設(shè)備。
②、SCK,Serial Clock,串行時(shí)鐘,和 I2C 的 SCL 一樣,為 SPI 通信提供時(shí)鐘。
③、MOSI/SDO,Master Out Slave In/Serial Data Output,主輸出從輸入。
④、MISO/SDI,Master In Slave Out/Serial Data Input,主輸入從輸出。
2、SPI 四種工作模式
SPI 有四種工作模式,通過時(shí)鐘極性(CPOL)和時(shí)鐘相位(CPHA)的搭配來得到四種工作模式:
①、CPOL=0,串行時(shí)鐘空閑狀態(tài)為低電平。
②、CPOL=1,串行時(shí)鐘空閑狀態(tài)為高電平。
③、CPHA=0,串行時(shí)鐘的第一個(gè)跳變沿(上升沿或下降沿)采集數(shù)據(jù)。
④、CPHA=1,串行時(shí)鐘的第二個(gè)跳變沿(上升沿或下降沿)采集數(shù)據(jù)。
示例波形圖如下:
SPI 是全雙工的,所以讀寫時(shí)序可以一起完成。
3、SPI 傳輸機(jī)制
從圖可以看出,主機(jī)和從機(jī)都有一個(gè)串行移位寄存器,主機(jī)通過向它的 SPI 串行寄存器寫入一個(gè)字節(jié)來發(fā)起一次傳輸。寄存器通過 MOSI 信號(hào)線將字節(jié)傳送給從機(jī),從機(jī)也將自己的移位寄存器中的內(nèi)容通過 MISO 信號(hào)線返回給主機(jī)。這樣,兩個(gè)移位寄存器中的內(nèi)容就被交換。
外設(shè)的寫操作和讀操作是同步完成的。如果只進(jìn)行寫操作,主機(jī)只需忽略接收到的字節(jié);反之,若主機(jī)要讀取從機(jī)的一個(gè)字節(jié),就必須發(fā)送一個(gè)空字節(jié)來引發(fā)從機(jī)的傳輸。
雖然 SPI 四線制支持讀寫同時(shí)進(jìn)行,但實(shí)際上我們很多時(shí)候并不需要又讀又寫,見以下兩種情況(參考 BMA223 數(shù)據(jù)手冊(cè)):
注意:如下三幅圖示均為 CPOL=1,CPHA=1
1、主機(jī)向從機(jī)寫數(shù)據(jù)
主機(jī)發(fā)送先發(fā)送 8 bits,第一個(gè) bit 為 0 代表這次主機(jī)是想寫數(shù)據(jù)到從機(jī),AD6~AD0 表示要寫的寄存器地址。然后,主機(jī)就會(huì)一直寫下去。在這期間 SDO 一直沒用,一直是高阻態(tài),算是一直讀到1。
2、主機(jī)從從機(jī)讀數(shù)據(jù)
這種情況下,主機(jī)先發(fā)送 8 bits,第一位為 1 代表這次是讀,然后 AD6 ~ AD0 是想要讀的寄存器地址,然后 SDO 開始返回?cái)?shù)據(jù)。
4、SPI timing diagram
Tcsb_setup:建立時(shí)間
Tcsb_hold:保持時(shí)間
tsckl:低電平時(shí)間
tsckh:高電平時(shí)間
SCK period :Tsckl + tsckh
一般情況下Tsckl=tsckh
注意:真實(shí)的波形圖如上,高低電平并不是到達(dá)最高點(diǎn)才算,0.3Vdd 以下為低電平,0.7Vdd 以上為高電平,計(jì)算信號(hào)時(shí)間長度的時(shí)候需要注意這個(gè)微小的時(shí)間,硬件設(shè)計(jì)必須注意信號(hào)質(zhì)量風(fēng)險(xiǎn),軟件開發(fā)人員也要會(huì)看波形圖。
這里的參數(shù),一般 spi 驅(qū)動(dòng)不需要設(shè)置,但是半導(dǎo)體廠商提供的 spi 控制器驅(qū)動(dòng)中,可以修改這些參數(shù)。我們寫 SPI 驅(qū)動(dòng)時(shí)候,可以根據(jù)從設(shè)備的要求來修改這些參數(shù)。
5、DMA 與 FIFO
不同平臺(tái)對(duì)于 SPI FIFO 和 DMA 的 buffer size 設(shè)置不同:
傳輸 32bytes 以下使用 FIFO,傳輸 32bytes 以上使用 DMA。
DMA 可以自動(dòng)發(fā)起多次傳輸,一次最大 256K 。
6、I2C 與 SPI 對(duì)比
功能 | I2C | SPI |
---|---|---|
線數(shù) | 2(SDA,SCL) | 4(MOSI,MISO,SCLK,CS) |
主機(jī)數(shù)量 | >=1 | ==1 |
類型 | 半雙工 | 全雙工 |
回應(yīng)機(jī)制 | yes | no |
速度 | <=3.4Mbps | high |
應(yīng)用 | 重要數(shù)據(jù) | 大量數(shù)據(jù) |
流控 | yes | no |
設(shè)備地址 | yes | no |
常規(guī)用途 | 命令 | 數(shù)據(jù) |
I2C 和 SPI 的速率如下:
I2C模式 | 速度 |
---|---|
標(biāo)準(zhǔn) | 100KHz |
快速 | 400KHz |
快速+ | 1MHz |
高速 | 3.4MHz |
SPI 速率:幾十 MHz 甚至上百 MHz,速度取決于 CPU 的 SPI 控制器和時(shí)鐘 clock
STM32F103 的 SPI 最高支持 18MHz,imx6ull 的 SPI 最高支持 52MHz,其他芯片一般用不到更高的,因?yàn)樗俣仍娇觳ㄐ钨|(zhì)量越不好,越容易出問題。
具體采用多大速率還和外設(shè)有關(guān),比如 EEPROM 的 W25Q128 的 SPI 最高支持 80MHz,ICM20608 傳感器的 SPI 最高支持8MHz。一般用在 flash 上的速度會(huì)較快。
7、擴(kuò)展
SPI 協(xié)議其實(shí)是包括:Standard SPI、Dual SPI 和 Queued SPI 三種協(xié)議接口。
Dual SPI 還是四線制,只是傳輸線可以變?yōu)橥较?,速度?Standard SPI 的兩倍。
Queued SPI 是六線制,多了兩根數(shù)據(jù)線,傳輸速度是 Standard SPI 的四倍。
SPILinux驅(qū)動(dòng)
1、SPI 驅(qū)動(dòng)源文件目錄
Linux common spi driver
kernel-4.14/drivers/spi/spi.cLinux提供的通用接口封裝層驅(qū)動(dòng)
kernel-4.14/drivers/spi/spidev.clinux提供的SPI通用設(shè)備驅(qū)動(dòng)程序
kernel-4.14/include/linux/spi/spi.hlinux提供的包含SPI的主要數(shù)據(jù)結(jié)構(gòu)和函數(shù)
spi 控制器驅(qū)動(dòng),IC 廠商提供,不同廠商命名不同
kernel-4.14/drivers/spi/spi-mt65xx.cMTKSPI控制器驅(qū)動(dòng)
kernel-4.14/drivers/spi/spi-mt65xx-dev.c
kernel-4.14/include/linux/platform_data/spi-mt65xx.h
dts
kernel-4.14/arch/arm/boot/dts/...
kernel-4.14/arch/arm64/boot/dts/...
以上文件對(duì)應(yīng)如下 SPI 驅(qū)動(dòng)軟件架構(gòu):
SPI 控制器驅(qū)動(dòng)程序
SPI 控制器不用關(guān)心設(shè)備的具體功能,它只負(fù)責(zé)把上層協(xié)議驅(qū)動(dòng)準(zhǔn)備好的數(shù)據(jù)按 SPI 總線的時(shí)序要求發(fā)送給 SPI 設(shè)備,同時(shí)把從設(shè)備收到的數(shù)據(jù)返回給上層的協(xié)議驅(qū)動(dòng),因此,內(nèi)核把 SPI 控制器的驅(qū)動(dòng)程序獨(dú)立出來。
SPI 控制器驅(qū)動(dòng)負(fù)責(zé)控制具體的控制器硬件,諸如 DMA 和中斷操作等等,因?yàn)槎鄠€(gè)上層的協(xié)議驅(qū)動(dòng)可能會(huì)通過控制器請(qǐng)求數(shù)據(jù)傳輸操作,所以,SPI 控制器驅(qū)動(dòng)同時(shí)也要負(fù)責(zé)對(duì)這些請(qǐng)求進(jìn)行隊(duì)列管理,保證先進(jìn)先出的原則。
SPI 通用接口封裝層
為了簡化 SPI 驅(qū)動(dòng)程序的編程工作,同時(shí)也為了降低【協(xié)議驅(qū)動(dòng)程序】和【控制器驅(qū)動(dòng)程序】的耦合程度,內(nèi)核把控制器驅(qū)動(dòng)和協(xié)議驅(qū)動(dòng)的一些通用操作封裝成標(biāo)準(zhǔn)的接口,加上一些通用的邏輯處理操作,組成了 SPI 通用接口封裝層。
這樣的好處是,對(duì)于控制器驅(qū)動(dòng)程序,只要實(shí)現(xiàn)標(biāo)準(zhǔn)的接口回調(diào) API,并把它注冊(cè)到通用接口層即可,無需直接和協(xié)議層驅(qū)動(dòng)程序進(jìn)行交互。而對(duì)于協(xié)議層驅(qū)動(dòng)來說,只需通過通用接口層提供的 API 即可完成設(shè)備和驅(qū)動(dòng)的注冊(cè),并通過通用接口層的 API 完成數(shù)據(jù)的傳輸,無需關(guān)注 SPI 控制器驅(qū)動(dòng)的實(shí)現(xiàn)細(xì)節(jié)。
SPI 協(xié)議驅(qū)動(dòng)程序
SPI 設(shè)備的具體功能是由 SPI 協(xié)議驅(qū)動(dòng)程序完成的,SPI 協(xié)議驅(qū)動(dòng)程序了解設(shè)備的功能和通信數(shù)據(jù)的協(xié)議格式。向下,協(xié)議驅(qū)動(dòng)通過通用接口層和控制器交換數(shù)據(jù),向上,協(xié)議驅(qū)動(dòng)通常會(huì)根據(jù)設(shè)備具體的功能和內(nèi)核的其它子系統(tǒng)進(jìn)行交互。
例如,和 MTD 層交互以便把 SPI 接口的存儲(chǔ)設(shè)備實(shí)現(xiàn)為某個(gè)文件系統(tǒng),和 TTY 子系統(tǒng)交互把 SPI 設(shè)備實(shí)現(xiàn)為一個(gè) TTY 設(shè)備,和網(wǎng)絡(luò)子系統(tǒng)交互以便把一個(gè) SPI 設(shè)備實(shí)現(xiàn)為一個(gè)網(wǎng)絡(luò)設(shè)備。如果是一個(gè)專有的 SPI 設(shè)備,我們也可以按設(shè)備的協(xié)議要求,實(shí)現(xiàn)自己的專有協(xié)議驅(qū)動(dòng)。
SPI 通用設(shè)備驅(qū)動(dòng)程序
考慮到連接在 SPI 控制器上的設(shè)備的可變性,在內(nèi)核沒有配備相應(yīng)的協(xié)議驅(qū)動(dòng)程序,對(duì)于這種情況,內(nèi)核為我們準(zhǔn)備了通用的 SPI 設(shè)備驅(qū)動(dòng)程序,該通用設(shè)備驅(qū)動(dòng)程序向用戶空間提供了控制 SPI 控制的控制接口,具體的協(xié)議控制和數(shù)據(jù)傳輸工作交由用戶空間根據(jù)具體的設(shè)備來完成,在這種方式中,只能采用同步的方式和 SPI 設(shè)備進(jìn)行通信,所以通常用于一些數(shù)據(jù)量較少的簡單 SPI 設(shè)備。
2、SPI 通用接口層
- SPI 通用接口層把具體的 SPI 設(shè)備的協(xié)議驅(qū)動(dòng)和 SPI 控制器驅(qū)動(dòng)連接在一起。
- 負(fù)責(zé) SPI 系統(tǒng)與 Linux 設(shè)備模型相關(guān)的初始化工作。
- 為協(xié)議驅(qū)動(dòng)和控制器驅(qū)動(dòng)提供一系列的標(biāo)準(zhǔn)接口 API 及其數(shù)據(jù)結(jié)構(gòu)。
- SPI 設(shè)備、SPI 協(xié)議驅(qū)動(dòng)、SPI 控制器的數(shù)據(jù)抽象
- 協(xié)助數(shù)據(jù)傳輸而定義的數(shù)據(jù)結(jié)構(gòu)
kernel-4.14/drivers/spi/spi.c
staticint__initspi_init(void)
{
intstatus;
buf=kmalloc(SPI_BUFSIZ,GFP_KERNEL);
if(!buf){
status=-ENOMEM;
gotoerr0;
}
//創(chuàng)建/sys/bus/spi節(jié)點(diǎn)
status=bus_register(&spi_bus_type);
if(status0)
gotoerr1;
//創(chuàng)建/sys/class/spi_master節(jié)點(diǎn)
status=class_register(&spi_master_class);
if(status0)
gotoerr2;
if(IS_ENABLED(CONFIG_SPI_SLAVE)){
status=class_register(&spi_slave_class);
if(status0)
gotoerr3;
}
......
}
在這里創(chuàng)建了 SPI 總線,創(chuàng)建 /sys/bus/spi 節(jié)點(diǎn)和 /sys/class/spi_master 節(jié)點(diǎn)。
重要數(shù)據(jù)結(jié)構(gòu):
spi_device
spi_driver
spi_board_info
spi_controller/spi_master
spi_transfer
spi_message
重要 API
spi_message_init
spi_message_add_tail
spi_sync
spi_async
spi_write
spi_read
接下來詳細(xì)解析結(jié)構(gòu)體和API,只講解重點(diǎn)部分,完整解析請(qǐng)參考官方文檔
https://www.kernel.org/doc/html/v4.14//driver-api/spi.html
只有熟悉每個(gè)結(jié)構(gòu)體存儲(chǔ)的是什么東西,才能真正搞懂 SPI 模塊。
spi_master/spi_controller:描述一個(gè) spi 主機(jī)設(shè)備
structspi_master{
//Linux驅(qū)動(dòng)模型中的設(shè)備
structdevicedev;
//此spi_master設(shè)備在全局spi_master鏈表中的節(jié)點(diǎn)
structlist_headlist;
//此spi_master編號(hào)
s16bus_num;
//此spi_master支持的片選信號(hào)數(shù)量
u16num_chipselect;
//dma地址對(duì)齊
u16dma_alignment;
//此spi_master支持傳輸?shù)膍ode
u16mode_bits;
u32bits_per_word_mask;
/*limitsontransferspeed*/
u32min_speed_hz;
u32max_speed_hz;
/*otherconstraintsrelevanttothisdriver*/
u16flags;
/*lockandmutexforSPIbuslocking*/
spinlock_tbus_lock_spinlock;//總線自旋鎖
structmutexbus_lock_mutex;//總線互斥鎖
//總線是否處于lock狀態(tài)
boolbus_lock_flag;
//準(zhǔn)備傳輸,設(shè)置傳輸?shù)膮?shù)
int(*setup)(structspi_device*spi);
//傳輸數(shù)據(jù)
int(*transfer)(structspi_device*spi,
structspi_message*mesg);
//設(shè)備release時(shí)的清除工作
void(*cleanup)(structspi_device*spi);
bool(*can_dma)(structspi_master*master,
structspi_device*spi,
structspi_transfer*xfer);
boolqueued;//是否采用系統(tǒng)的序列化傳輸
structkthread_workerkworker;//序列化傳輸時(shí)的線程worker
structtask_struct*kworker_task;//序列化傳輸?shù)木€程
structkthread_workpump_messages;//序列化傳輸時(shí)的處理函數(shù)
spinlock_tqueue_lock;//序列化傳輸時(shí)的queue_lock
structlist_headqueue;//序列化傳輸時(shí)的msg隊(duì)列頭
structspi_message*cur_msg;//序列化傳輸時(shí)當(dāng)前的msg
boolidling;
boolbusy;//序列化傳輸時(shí)線程是否處于busy狀態(tài)
boolrunning;//序列化傳輸時(shí)線程是否在運(yùn)行
boolrt;//是否實(shí)時(shí)傳輸
......
int(*prepare_transfer_hardware)(structspi_master*master);
//一個(gè)msg的傳輸實(shí)現(xiàn)
int(*transfer_one_message)(structspi_master*master,
structspi_message*mesg);
......
/*gpiochipselect*/
int*cs_gpios;
......
};
spi_device:描述一個(gè) spi 從機(jī)設(shè)備
structspi_device{
//Linux驅(qū)動(dòng)模型中的設(shè)備
structdevicedev;
structspi_master*master;//設(shè)備所連接的spi主機(jī)設(shè)備
u32max_speed_hz;//該設(shè)備最大傳輸速率
u8chip_select;//CS片選信號(hào)編號(hào)
u8bits_per_word;//每次傳輸長度
u16mode;//傳輸模式
......
intirq;//軟件中斷號(hào)
void*controller_state;//控制器狀態(tài)
void*controller_data;//控制參數(shù)
charmodalias[SPI_NAME_SIZE];//設(shè)備名稱
//CS片選信號(hào)對(duì)應(yīng)的GPIOnumber
intcs_gpio;/*chipselectgpio*/
/*thestatistics*/
structspi_statisticsstatistics;
};
spi_driver:描述一個(gè) spi 設(shè)備驅(qū)動(dòng)
structspi_driver{
//此driver所支持的spi設(shè)備list
conststructspi_device_id*id_table;
int(*probe)(structspi_device*spi);
int(*remove)(structspi_device*spi);
//系統(tǒng)shutdown時(shí)的回調(diào)函數(shù)
void(*shutdown)(structspi_device*spi);
structdevice_driverdriver;
};
spi_board_info:描述一個(gè) spi 從機(jī)設(shè)備板級(jí)信息,無設(shè)備樹時(shí)使用
structspi_board_info{
//設(shè)備名稱
charmodalias[SPI_NAME_SIZE];
constvoid*platform_data;//設(shè)備的平臺(tái)數(shù)據(jù)
void*controller_data;//設(shè)備的控制器數(shù)據(jù)
intirq;//設(shè)備的中斷號(hào)
u32max_speed_hz;//設(shè)備支持的最大速率
u16bus_num;//設(shè)備連接的spi總線編號(hào)
u16chip_select;//設(shè)備連接的CS信號(hào)編號(hào)
u16mode;//設(shè)備使用的傳輸mode
};
spi_transfer:描述 spi 傳輸?shù)木唧w數(shù)據(jù)
structspi_transfer{
constvoid*tx_buf;//spi_transfer的發(fā)送buf
void*rx_buf;//spi_transfer的接收buf
unsignedlen;//spi_transfer發(fā)送和接收的長度
dma_addr_ttx_dma;//tx_buf對(duì)應(yīng)的dma地址
dma_addr_trx_dma;//rx_buf對(duì)應(yīng)的dma地址
structsg_tabletx_sg;
structsg_tablerx_sg;
//spi_transfer傳輸完成后是否要改變CS片選信號(hào)
unsignedcs_change:1;
unsignedtx_nbits:3;
unsignedrx_nbits:3;
......
u8bits_per_word;//spi_transfer中一個(gè)word占的bits
u16delay_usecs;//兩個(gè)spi_transfer直接的等待延遲
u32speed_hz;//spi_transfer的傳輸速率
structlist_headtransfer_list;//spi_transfer掛載到的message節(jié)點(diǎn)
};
spi_message:描述一次 spi 傳輸?shù)男畔?/p>
structspi_message{
//掛載在此msg上的transfer鏈表頭
structlist_headtransfers;
//此msg需要通信的spi從機(jī)設(shè)備
structspi_device*spi;
//所使用的地址是否是dma地址
unsignedis_dma_mapped:1;
//msg發(fā)送完成后的處理函數(shù)
void(*complete)(void*context);
void*context;//complete函數(shù)的參數(shù)
unsignedframe_length;
unsignedactual_length;//此msg實(shí)際成功發(fā)送的字節(jié)數(shù)
intstatus;//此 msg 的發(fā)送狀態(tài),0:成功,負(fù)數(shù),失敗
structlist_headqueue;//此msg在所有msg中的鏈表節(jié)點(diǎn)
void*state;//此msg的私有數(shù)據(jù)
};
隊(duì)列化
SPI 數(shù)據(jù)傳輸可以有兩種方式:同步方式和異步方式。
同步方式:數(shù)據(jù)傳輸?shù)陌l(fā)起者必須等待本次傳輸?shù)慕Y(jié)束,期間不能做其它事情,用代碼來解釋就是,調(diào)用傳輸?shù)暮瘮?shù)后,直到數(shù)據(jù)傳輸完成,函數(shù)才會(huì)返回。
異步方式:數(shù)據(jù)傳輸?shù)陌l(fā)起者無需等待傳輸?shù)慕Y(jié)束,數(shù)據(jù)傳輸期間還可以做其它事情,用代碼來解釋就是,調(diào)用傳輸?shù)暮瘮?shù)后,函數(shù)會(huì)立刻返回而不用等待數(shù)據(jù)傳輸完成,我們只需設(shè)置一個(gè)回調(diào)函數(shù),傳輸完成后,該回調(diào)函數(shù)會(huì)被調(diào)用以通知發(fā)起者數(shù)據(jù)傳送已經(jīng)完成。
同步方式簡單易用,很適合處理那些少量數(shù)據(jù)的單次傳輸。但是對(duì)于數(shù)據(jù)量大、次數(shù)多的傳輸來說,異步方式就顯得更加合適。
對(duì)于 SPI 控制器來說,要支持異步方式必須要考慮以下兩種狀況:
- 對(duì)于同一個(gè)數(shù)據(jù)傳輸?shù)陌l(fā)起者,既然異步方式無需等待數(shù)據(jù)傳輸完成即可返回,返回后,該發(fā)起者可以立刻又發(fā)起一個(gè) message,而這時(shí)上一個(gè)message還沒有處理完。
- 對(duì)于另外一個(gè)不同的發(fā)起者來說,也有可能同時(shí)發(fā)起一次message傳輸請(qǐng)求。
隊(duì)列化正是為了為了解決以上的問題,所謂隊(duì)列化,是指把等待傳輸?shù)?message 放入一個(gè)等待隊(duì)列中,發(fā)起一個(gè)傳輸操作,其實(shí)就是把對(duì)應(yīng)的 message 按先后順序放入一個(gè)等待隊(duì)列中,系統(tǒng)會(huì)在不斷檢測隊(duì)列中是否有等待傳輸?shù)?message,如果有就不停地調(diào)度數(shù)據(jù)傳輸內(nèi)核線程,逐個(gè)取出隊(duì)列中的 message 進(jìn)行處理,直到隊(duì)列變空為止。SPI 通用接口層為我們實(shí)現(xiàn)了隊(duì)列化的基本框架。
spi_message 就是一次 SPI 數(shù)據(jù)交換的原子操作,不可打斷。
3、SPI 控制器驅(qū)動(dòng)層
SPI 控制器驅(qū)動(dòng)層負(fù)責(zé)最底層的數(shù)據(jù)收發(fā),主要有以下功能:
- 申請(qǐng)必要的硬件資源,比如中斷、DMA 通道、DMA 內(nèi)存緩沖區(qū)等等
- 配置 SPI 控制器的工作模式和參數(shù),使之可以和相應(yīng)的設(shè)備進(jìn)行正確的數(shù)據(jù)交換
- 向通用接口層提供接口,使得上層的協(xié)議驅(qū)動(dòng)可以通過通用接口層訪問控制器驅(qū)動(dòng)
- 配合通用接口層,完成數(shù)據(jù)消息隊(duì)列的排隊(duì)和處理,直到消息隊(duì)列變空為止
SPI 主機(jī)驅(qū)動(dòng)就是 SOC 的 SPI 控制器驅(qū)動(dòng)。Linux 內(nèi)核使用 spi_master/spi_controller 表示 SPI 主機(jī)驅(qū)動(dòng),spi_master 是個(gè)結(jié)構(gòu)體,定義在 include/linux/spi/spi.h 文件中。
SPI 主機(jī)驅(qū)動(dòng)的核心就是申請(qǐng) spi_master,然后初始化 spi_master,最后向 Linux 內(nèi)核注冊(cè) spi_master。
API 如下:
spi_alloc_master 函數(shù):申請(qǐng) spi_master。
spi_master_put 函數(shù):釋放 spi_master。
spi_register_master函數(shù):注冊(cè) spi_master。
spi_unregister_master 函數(shù):注銷 spi_master。
spi_bitbang_start函數(shù):注冊(cè) spi_master。
spi_bitbang_stop 函數(shù):注銷 spi_master。
SPI 主機(jī)驅(qū)動(dòng)的加載
以 MTK 為例,源碼來自于小米開源項(xiàng)目
https://github.com/MiCode/Xiaomi_Kernel_OpenSource
小米每做一個(gè)項(xiàng)目,都會(huì)把 kernel 部分開源,因?yàn)樾枰裱?Linux GPL 開源協(xié)議。
【設(shè)備】聲明在設(shè)備樹中
kernel-4.14/arch/arm64/boot/dts/mediatek/mt6885.dts
【驅(qū)動(dòng)】
kernel-4.14/drivers/spi/spi-mt65xx.c
匹配以后,probe 函數(shù)執(zhí)行,申請(qǐng) spi_master,初始化 spi_master,最后向 Linux 內(nèi)核注冊(cè) spi_master。
4、軟件流程
看懂該圖,對(duì) SPI 驅(qū)動(dòng)框架就有完整的了解了。
1、2、3 按順執(zhí)行,首先有 spi 總線的注冊(cè),然后是 spi 控制器驅(qū)動(dòng)加載,然后是設(shè)備驅(qū)動(dòng)加載。
區(qū)別在于,spi 控制器驅(qū)動(dòng)加載時(shí),是靠 platform 總線匹配設(shè)備(控制器)與驅(qū)動(dòng)。spi 設(shè)備驅(qū)動(dòng)加載時(shí),是靠 spi 總線匹配設(shè)備(外設(shè)IC)與驅(qū)動(dòng)。
init flow
spi_register_master 的調(diào)用序列圖
隊(duì)列化的工作機(jī)制及過程
當(dāng)協(xié)議驅(qū)動(dòng)程序通過 spi_async 發(fā)起一個(gè) message 請(qǐng)求時(shí),隊(duì)列化和工作線程被激活,觸發(fā)一些列的操作,最終完成 message 的傳輸操作。
spi_sync 與 spi_async 類似,只是有一個(gè)等待過程。
5、SPI 設(shè)備驅(qū)動(dòng)
【設(shè)備】聲明在設(shè)備樹中
注意:設(shè)備的聲明,slave device node 應(yīng)該包含在你所要掛載的 &spi node 下,將 device 綁定在 master 上。然后通過 pinctrl 方式指定 GPIO,并在驅(qū)動(dòng)中操作 pinctrl 句柄。
【驅(qū)動(dòng)】demo
Linux 內(nèi)核使用 spi_driver 結(jié)構(gòu)體來表示 spi 設(shè)備驅(qū)動(dòng),我們?cè)诰帉?SPI 設(shè)備驅(qū)動(dòng)的時(shí)候需要實(shí)現(xiàn) spi_driver。spi_driver 結(jié)構(gòu)體定義在 include/linux/spi/spi.h 文件中。
spi_register_driver:注冊(cè) spi_driver
spi_unregister_driver:銷掉 spi_driver
/*probe函數(shù)*/
staticintxxx_probe(structspi_device*spi)
{
/*具體函數(shù)內(nèi)容*/
return0;
}
/*remove函數(shù)*/
staticintxxx_remove(structspi_device*spi)
{
/*具體函數(shù)內(nèi)容*/
return0;
}
/*傳統(tǒng)匹配方式ID列表*/
staticconststructspi_device_idxxx_id[]={
{"xxx",0},
{}
};
/*設(shè)備樹匹配列表*/
staticconststructof_device_idxxx_of_match[]={
{.compatible="xxx"},
{/*Sentinel*/}
};
/*SPI驅(qū)動(dòng)結(jié)構(gòu)體*/
staticstructspi_driverxxx_driver={
.probe=xxx_probe,
.remove=xxx_remove,
.driver={
.owner=THIS_MODULE,
.name="xxx",
.of_match_table=xxx_of_match,
},
.id_table=xxx_id,
};
/*驅(qū)動(dòng)入口函數(shù)*/
staticint__initxxx_init(void)
{
returnspi_register_driver(&xxx_driver);
}
/*驅(qū)動(dòng)出口函數(shù)*/
staticvoid__exitxxx_exit(void)
{
spi_unregister_driver(&xxx_driver);
}
module_init(xxx_init);
module_exit(xxx_exit);
在驅(qū)動(dòng)入口函數(shù)中調(diào)用 spi_register_driver 來注冊(cè) spi_driver。
在驅(qū)動(dòng)出口函數(shù)中調(diào)用 spi_unregister_driver 來注銷 spi_driver。
spi 讀寫數(shù)據(jù)demo
/*SPI多字節(jié)發(fā)送*/
staticintspi_send(structspi_device*spi,u8*buf,intlen)
{
intret;
structspi_messagem;
structspi_transfert={
.tx_buf=buf,
.len=len,
};
spi_message_init(&m);/*初始化spi_message*/
spi_message_add_tail(t,&m);/*將spi_transfer添加到spi_message隊(duì)列*/
ret=spi_sync(spi,&m);/*同步傳輸*/
returnret;
}
/*SPI多字節(jié)接收*/
staticintspi_receive(structspi_device*spi,u8*buf,intlen)
{
intret;
structspi_messagem;
structspi_transfert={
.rx_buf=buf,
.len=len,
};
spi_message_init(&m);/*初始化spi_message*/
spi_message_add_tail(t,&m);/*將spi_transfer添加到spi_message隊(duì)列*/
ret=spi_sync(spi,&m);/*同步傳輸*/
returnret;
}
除了 init、exit、probe、remove、read、write 函數(shù)外,其他的函數(shù)看需求實(shí)現(xiàn),這幾個(gè)是最基本的。
6、總結(jié)
Linux 是 總線、設(shè)備、驅(qū)動(dòng) 的框架,理解了這個(gè)框架,就能理解所有的模塊驅(qū)動(dòng)框架。
SPI 驅(qū)動(dòng)比 I2C 驅(qū)動(dòng)還是簡單很多的。
end
原文標(biāo)題:SPI 硬件+Linux驅(qū)動(dòng)詳解
文章出處:【微信公眾號(hào):一口Linux】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
-
Linux
+關(guān)注
關(guān)注
87文章
11207瀏覽量
208721 -
SPI硬件
+關(guān)注
關(guān)注
0文章
2瀏覽量
795 -
傳輸機(jī)制
+關(guān)注
關(guān)注
0文章
2瀏覽量
1137
原文標(biāo)題:SPI 硬件+Linux驅(qū)動(dòng)詳解
文章出處:【微信號(hào):yikoulinux,微信公眾號(hào):一口Linux】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論