Linux common spi driver。
kernel-4.14/drivers/spi/spi.c Linux 提供的通用接口封裝層驅動kernel-4.14/drivers/spi/spidev.c linux 提供的 SPI 通用設備驅動程序kernel-4.14/include/linux/spi/spi.h linux 提供的包含 SPI 的主要數據結構和函數
spi 控制器驅動,IC 廠商提供,不同廠商命名不同。
kernel-4.14/drivers/spi/spi-mt65xx.c MTK SPI 控制器驅動kernel-4.14/drivers/spi/spi-mt65xx-dev.ckernel-4.14/include/linux/platform_data/spi-mt65xx.h
dts。
kernel-4.14/arch/arm/boot/dts/...kernel-4.14/arch/arm64/boot/dts/...
以上文件對應如下 SPI 驅動軟件架構:
SPI 控制器驅動程序SPI 控制器不用關心設備的具體功能,它只負責把上層協議驅動準備好的數據按 SPI 總線的時序要求發送給 SPI 設備,同時把從設備收到的數據返回給上層的協議驅動,因此,內核把 SPI 控制器的驅動程序獨立出來。
SPI 控制器驅動負責控制具體的控制器硬件,諸如 DMA 和中斷操作等等,因為多個上層的協議驅動可能會通過控制器請求數據傳輸操作,所以,SPI 控制器驅動同時也要負責對這些請求進行隊列管理,保證先進先出的原則。
SPI 通用接口封裝層為了簡化 SPI 驅動程序的編程工作,同時也為了降低【協議驅動程序】和【控制器驅動程序】的耦合程度,內核把控制器驅動和協議驅動的一些通用操作封裝成標準的接口,加上一些通用的邏輯處理操作,組成了 SPI 通用接口封裝層。
這樣的好處是,對于控制器驅動程序,只要實現標準的接口回調 API,并把它注冊到通用接口層即可,無需直接和協議層驅動程序進行交互。而對于協議層驅動來說,只需通過通用接口層提供的 API 即可完成設備和驅動的注冊,并通過通用接口層的 API 完成數據的傳輸,無需關注 SPI 控制器驅動的實現細節。
SPI 協議驅動程序SPI 設備的具體功能是由 SPI 協議驅動程序完成的,SPI 協議驅動程序了解設備的功能和通信數據的協議格式。向下,協議驅動通過通用接口層和控制器交換數據,向上,協議驅動通常會根據設備具體的功能和內核的其它子系統進行交互。
例如,和 MTD 層交互以便把 SPI 接口的存儲設備實現為某個文件系統,和 TTY 子系統交互把 SPI 設備實現為一個 TTY 設備,和網絡子系統交互以便把一個 SPI 設備實現為一個網絡設備。如果是一個專有的 SPI 設備,我們也可以按設備的協議要求,實現自己的專有協議驅動。
SPI 通用設備驅動程序考慮到連接在 SPI 控制器上的設備的可變性,在內核沒有配備相應的協議驅動程序,對于這種情況,內核為我們準備了通用的 SPI 設備驅動程序,該通用設備驅動程序向用戶空間提供了控制 SPI 控制的控制接口,具體的協議控制和數據傳輸工作交由用戶空間根據具體的設備來完成,在這種方式中,只能采用同步的方式和 SPI 設備進行通信,所以通常用于一些數據量較少的簡單 SPI 設備。
2、SPI 通用接口層SPI 通用接口層把具體的 SPI 設備的協議驅動和 SPI 控制器驅動連接在一起。負責 SPI 系統與 Linux 設備模型相關的初始化工作。為協議驅動和控制器驅動提供一系列的標準接口 API 及其數據結構。SPI 設備、SPI 協議驅動、SPI 控制器的數據抽象協助數據傳輸而定義的數據結構。kernel-4.14/drivers/spi/spi.c。
static int __init spi_init(void){ int status; buf = kmalloc(SPI_BUFSIZ, GFP_KERNEL); if (!buf) { status = -ENOMEM; goto err0; } // 創建 /sys/bus/spi 節點 status = bus_register(&spi_bus_type); if (status < 0) goto err1; //創建 /sys/class/spi_master 節點 status = class_register(&spi_master_class); if (status < 0) goto err2; if (IS_ENABLED(CONFIG_SPI_SLAVE)) { status = class_register(&spi_slave_class); if (status < 0) goto err3; } ......}
在這里創建了 SPI 總線,創建 /sys/bus/spi 節點和 /sys/class/spi_master 節點。
重要數據結構:
spi_devicespi_driverspi_board_infospi_controller/spi_masterspi_transferspi_message
重要 API。
spi_message_initspi_message_add_tailspi_syncspi_asyncspi_writespi_read
接下來詳細解析結構體和API,只講解重點部分,完整解析請參考官方文檔:
https://www.kernel.org/doc/html/v4.14//driver-api/spi.html。
只有熟悉每個結構體存儲的是什么東西,才能真正搞懂 SPI 模塊。
spi_master/spi_controller:描述一個 spi 主機設備。
struct spi_master { //Linux 驅動模型中的設備 struct device dev; //此 spi_master 設備在全局 spi_master 鏈表中的節點 struct list_head list; //此 spi_master 編號 s16 bus_num; //此 spi_master 支持的片選信號數量 u16 num_chipselect; //dma 地址對齊 u16 dma_alignment; //此 spi_master 支持傳輸的 mode u16 mode_bits; u32 bits_per_word_mask; /* limits on transfer speed */ u32 min_speed_hz; u32 max_speed_hz; /* other constraints relevant to this driver */ u16 flags; /* lock and mutex for SPI bus locking */ spinlock_t bus_lock_spinlock;//總線自旋鎖 struct mutex bus_lock_mutex;//總線互斥鎖 //總線是否處于 lock 狀態 bool bus_lock_flag; //準備傳輸,設置傳輸的參數 int (*setup)(struct spi_device *spi); //傳輸數據 int (*transfer)(struct spi_device *spi, struct spi_message *mesg); // 設備 release 時的清除工作 void (*cleanup)(struct spi_device *spi); bool (*can_dma)(struct spi_master *master, struct spi_device *spi, struct spi_transfer *xfer); bool queued;//是否采用系統的序列化傳輸 struct kthread_worker kworker;//序列化傳輸時的線程 worker struct task_struct *kworker_task;//序列化傳輸的線程 struct kthread_work pump_messages;//序列化傳輸時的處理函數 spinlock_t queue_lock;//序列化傳輸時的queue_lock struct list_head queue;//序列化傳輸時的 msg 隊列頭 struct spi_message *cur_msg;//序列化傳輸時當前的 msg bool idling; bool busy;//序列化傳輸時線程是否處于busy狀態 bool running;//序列化傳輸時線程是否在運行 bool rt;//是否實時傳輸 ...... int (*prepare_transfer_hardware)(struct spi_master *master); //一個 msg 的傳輸實現 int (*transfer_one_message)(struct spi_master *master, struct spi_message *mesg); ...... /* gpio chip select */ int *cs_gpios; ......};
spi_device:描述一個 spi 從機設備。
struct spi_device { //Linux驅動模型中的設備 struct device dev; struct spi_master *master;//設備所連接的 spi 主機設備 u32 max_speed_hz;//該設備最大傳輸速率 u8 chip_select;//CS片選信號編號 u8 bits_per_word;//每次傳輸長度 u16 mode;//傳輸模式 ...... int irq;//軟件中斷號 void *controller_state;//控制器狀態 void *controller_data;//控制參數 char modalias[SPI_NAME_SIZE];//設備名稱 //CS 片選信號對應的 GPIO number int cs_gpio; /* chip select gpio */ /* the statistics */ struct spi_statistics statistics;};
spi_driver:描述一個 spi 設備驅動。
struct spi_driver { //此driver所支持的 spi 設備 list const struct spi_device_id *id_table; int (*probe)(struct spi_device *spi); int (*remove)(struct spi_device *spi); //系統 shutdown 時的回調函數 void (*shutdown)(struct spi_device *spi); struct device_driver driver;};
spi_board_info:描述一個 spi 從機設備板級信息,無設備樹時使用。
struct spi_board_info { //設備名稱 char modalias[SPI_NAME_SIZE]; const void *platform_data;//設備的平臺數據 void *controller_data;//設備的控制器數據 int irq;//設備的中斷號 u32 max_speed_hz;//設備支持的最大速率 u16 bus_num;//設備連接的 spi 總線編號 u16 chip_select;//設備連接的 CS 信號編號 u16 mode;//設備使用的傳輸 mode};
spi_transfer:描述 spi 傳輸的具體數據。
struct spi_transfer { const void *tx_buf;//spi_transfer 的發送 buf void *rx_buf;//spi_transfer 的接收 buf unsigned len;//spi_transfer 發送和接收的長度 dma_addr_t tx_dma;//tx_buf 對應的 dma 地址 dma_addr_t rx_dma;//rx_buf 對應的 dma 地址 struct sg_table tx_sg; struct sg_table rx_sg; //spi_transfer傳輸完成后是否要改變 CS 片選信號 unsigned cs_change:1; unsigned tx_nbits:3; unsigned rx_nbits:3; ...... u8 bits_per_word;//spi_transfer 中一個 word 占的bits u16 delay_usecs;//兩個 spi_transfer 直接的等待延遲 u32 speed_hz;//spi_transfer 的傳輸速率 struct list_head transfer_list;//spi_transfer掛載到的 message 節點};
spi_message:描述一次 spi 傳輸的信息。
struct spi_message { //掛載在此 msg 上的 transfer 鏈表頭 struct list_head transfers; //此 msg 需要通信的 spi 從機設備 struct spi_device *spi; //所使用的地址是否是 dma 地址 unsigned is_dma_mapped:1; //msg 發送完成后的處理函數 void (*complete)(void *context); void *context;//complete函數的參數 unsigned frame_length; unsigned actual_length;//此 msg 實際成功發送的字節數 int status;//此 msg 的發送狀態,0:成功,負數,失敗 struct list_head queue;//此 msg 在所有 msg 中的鏈表節點 void *state;//此 msg 的私有數據};隊列化
SPI 數據傳輸可以有兩種方式:同步方式和異步方式。
同步方式:數據傳輸的發起者必須等待本次傳輸的結束,期間不能做其它事情,用代碼來解釋就是,調用傳輸的函數后,直到數據傳輸完成,函數才會返回。
異步方式:數據傳輸的發起者無需等待傳輸的結束,數據傳輸期間還可以做其它事情,用代碼來解釋就是,調用傳輸的函數后,函數會立刻返回而不用等待數據傳輸完成,我們只需設置一個回調函數,傳輸完成后,該回調函數會被調用以通知發起者數據傳送已經完成。
同步方式簡單易用,很適合處理那些少量數據的單次傳輸。但是對于數據量大、次數多的傳輸來說,異步方式就顯得更加合適。
對于 SPI 控制器來說,要支持異步方式必須要考慮以下兩種狀況:
對于同一個數據傳輸的發起者,既然異步方式無需等待數據傳輸完成即可返回,返回后,該發起者可以立刻又發起一個 message,而這時上一個message還沒有處理完。對于另外一個不同的發起者來說,也有可能同時發起一次message傳輸請求。隊列化正是為了為了解決以上的問題,所謂隊列化,是指把等待傳輸的 message 放入一個等待隊列中,發起一個傳輸操作,其實就是把對應的 message 按先后順序放入一個等待隊列中,系統會在不斷檢測隊列中是否有等待傳輸的 message,如果有就不停地調度數據傳輸內核線程,逐個取出隊列中的 message 進行處理,直到隊列變空為止。SPI 通用接口層為我們實現了隊列化的基本框架。
spi_message 就是一次 SPI 數據交換的原子操作,不可打斷。
3、SPI 控制器驅動層SPI 控制器驅動層負責最底層的數據收發,主要有以下功能:
申請必要的硬件資源,比如中斷、DMA 通道、DMA 內存緩沖區等等。配置 SPI 控制器的工作模式和參數,使之可以和相應的設備進行正確的數據交換。向通用接口層提供接口,使得上層的協議驅動可以通過通用接口層訪問控制器驅動。配合通用接口層,完成數據消息隊列的排隊和處理,直到消息隊列變空為止。SPI 主機驅動就是 SOC 的 SPI 控制器驅動。Linux 內核使用 spi_master/spi_controller 表示 SPI 主機驅動,spi_master 是個結構體,定義在 include/linux/spi/spi.h 文件中。
SPI 主機驅動的核心就是申請 spi_master,然后初始化 spi_master,最后向 Linux 內核注冊 spi_master。
API 如下:
spi_alloc_master 函數:申請 spi_master。spi_master_put 函數:釋放 spi_master。spi_register_master函數:注冊 spi_master。spi_unregister_master 函數:注銷 spi_master。spi_bitbang_start函數:注冊 spi_master。spi_bitbang_stop 函數:注銷 spi_master。SPI 主機驅動的加載
以 MTK 為例,源碼來自于小米開源項目:
https://github.com/MiCode/Xiaomi_Kernel_OpenSource。
小米每做一個項目,都會把 kernel 部分開源,因為需要遵循 Linux GPL 開源協議。
【設備】聲明在設備樹中
kernel-4.14/arch/arm64/boot/dts/mediatek/mt6885.dts【驅動】
kernel-4.14/drivers/spi/spi-mt65xx.c。
匹配以后,probe 函數執行,申請 spi_master,初始化 spi_master,最后向 Linux 內核注冊 spi_master。
4、軟件流程看懂該圖,對 SPI 驅動框架就有完整的了解了。
1、2、3 按順執行,首先有 spi 總線的注冊,然后是 spi 控制器驅動加載,然后是設備驅動加載。
區別在于,spi 控制器驅動加載時,是靠 platform 總線匹配設備(控制器)與驅動。spi 設備驅動加載時,是靠 spi 總線匹配設備(外設IC)與驅動。
init flowspi_register_master 的調用序列圖隊列化的工作機制及過程當協議驅動程序通過 spi_async 發起一個 message 請求時,隊列化和工作線程被激活,觸發一些列的操作,最終完成 message 的傳輸操作。
spi_sync 與 spi_async 類似,只是有一個等待過程。
5、SPI 設備驅動【設備】聲明在設備樹中。
注意:設備的聲明,slave device node 應該包含在你所要掛載的 &spi node 下,將 device 綁定在 master 上。然后通過 pinctrl 方式指定 GPIO,并在驅動中操作 pinctrl 句柄。
【驅動】demoLinux 內核使用 spi_driver 結構體來表示 spi 設備驅動,我們在編寫 SPI 設備驅動的時候需要實現 spi_driver。spi_driver 結構體定義在 include/linux/spi/spi.h 文件中。
spi_register_driver:注冊 spi_driverspi_unregister_driver:銷掉 spi_driver
/* probe 函數 */static int xxx_probe(struct spi_device *spi){ /* 具體函數內容 */ return 0;}/* remove 函數 */static int xxx_remove(struct spi_device *spi){ /* 具體函數內容 */ return 0;}/* 傳統匹配方式 ID 列表 */static const struct spi_device_id xxx_id[] = { {"xxx", 0}, {}};/* 設備樹匹配列表 */static const struct of_device_id xxx_of_match[] = { { .compatible = "xxx" }, { /* Sentinel */ }};/* SPI 驅動結構體 */static struct spi_driver xxx_driver = { .probe = xxx_probe, .remove = xxx_remove, .driver = { .owner = THIS_MODULE, .name = "xxx", .of_match_table = xxx_of_match, }, .id_table = xxx_id,};/* 驅動入口函數 */static int __init xxx_init(void){ return spi_register_driver(&xxx_driver);}/* 驅動出口函數 */static void __exit xxx_exit(void){ spi_unregister_driver(&xxx_driver);}module_init(xxx_init);module_exit(xxx_exit);
在驅動入口函數中調用 spi_register_driver 來注冊 spi_driver。
在驅動出口函數中調用 spi_unregister_driver 來注銷 spi_driver。
spi 讀寫數據demo。
/* 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;}
除了 init、exit、probe、remove、read、write 函數外,其他的函數看需求實現,這幾個是最基本的。
6、總結Linux 是 總線、設備、驅動 的框架,理解了這個框架,就能理解所有的模塊驅動框架。
SPI 驅動比 I2C 驅動還是簡單很多的。
標簽: