一、linux內核中的SPI框架
在嵌入式linux開發中,SPI是一種常見的通信方式,如下圖所示:
常見的屬于SPI設備包括:RF芯片、智能卡、EEPROM、RTC、觸摸傳感器等等。
在內核中,與I2C一樣,也同樣提供了一個SPI框架,本文圍繞這個框架展開,來分析內核提供的SPI框架是如何運作的。內核中與SPI相關的代碼規范放置在/drivers/spi路徑下,
從Makefile可知,內核提供的SPI框架主要實現在spi.c、spidev.c文件中。
spi.c文件實現了spi核心的初始化,以及實現spi框架的相關API接口。(如果想讓系統支持spi,此文件必須被編譯)
spidev.c文件用于實現SPI設備同步用戶空間接口。(該文件為可選特性)
存在/drivers/spi路徑下其他洋洋灑灑的文件則是不同廠家提供的SPI控制器的驅動程序,這些文件往往由芯片廠家開發,然后合并到linux內核源碼中,以適配自家芯片。
二、SPI核心的初始化
SPI核心的初始化實現在/drivers/spi/spi.c文件中,如下代碼:
staticint__initspi_init(void) { intstatus; buf=kmalloc(SPI_BUFSIZ,GFP_KERNEL); if(!buf){ status=-ENOMEM; gotoerr0; } status=bus_register(&spi_bus_type); if(status0) ??goto?err1; ?status?=?class_register(&spi_master_class); ?if?(status?0) ??goto?err2; ?if?(IS_ENABLED(CONFIG_OF_DYNAMIC)) ??WARN_ON(of_reconfig_notifier_register(&spi_of_notifier)); ?return?0; err2: ?bus_unregister(&spi_bus_type); err1: ?kfree(buf); ?buf?=?NULL; err0: ?return?status; } /*?board_info?is?normally?registered?in?arch_initcall(), ?*?but?even?essential?drivers?wait?till?later ?* ?*?REVISIT?only?boardinfo?really?needs?static?linking.?the?rest?(device?and ?*?driver?registration)?_could_?be?dynamically?linked?(modular)?...?costs ?*?include?needing?to?have?boardinfo?data?structures?be?much?more?public. ?*/ postcore_initcall(spi_init);
在postcore_initcall()導出的spi核心的初始化過程中,主要做了以下幾件事情:
1、分配一個用于SPI的buffer。
2、注冊spi總線。
3、注冊spi_master主機類。
以上操作是內核中面向對象的基礎構件過程,spi框架也不例外,也必須這樣實現,以獲得內核設備驅動模型的管理。
關于spi bus總線對設備和驅動的匹配過程:在spi/spic.c文件中定義了用于描述spi的bus總線,命名為spi,該總線在spi_init()函數中注冊,匹配過程由spi_match_device()描述,該函數實現如下:
staticintspi_match_device(structdevice*dev,structdevice_driver*drv) { conststructspi_device*spi=to_spi_device(dev); conststructspi_driver*sdrv=to_spi_driver(drv); /*Checkoverridefirst,andifset,onlyusethenameddriver*/ if(spi->driver_override) returnstrcmp(spi->driver_override,drv->name)==0; /*AttemptanOFstylematch*/ if(of_driver_match_device(dev,drv)) return1; /*ThentryACPI*/ if(acpi_driver_match_device(dev,drv)) return1; if(sdrv->id_table) return!!spi_match_id(sdrv->id_table,spi->modalias); returnstrcmp(spi->modalias,drv->name)==0; }
在上述代碼中,描述了對spi設備和驅動匹配的四種方式。
三、SPI核心的數據結構
注意:幾乎芯片原廠都要提供一個主機側的SPI驅動,以支持自家的芯片。
編程接口是圍繞兩種驅動程序和兩種設備構建。SPI控制器驅動程序抽象了控制器硬件,它可以像一組GPIO引腳一樣簡單,也可以像fifo一樣復雜,也有可能支持DMA引擎(實現數據的最大化吞吐量)。這樣的驅動程序在它們所在的總線(通常是平臺總線)和SPI之間架橋,并將其設備的SPI端作為struct spi_controller公開。
SPI設備是主設備的子設備,由struct spi_device表示,并由struct spi_board_info描述符進行描述,這些描述符通常由特定板卡的初始化代碼提供。
struct spi_driver稱為協議驅動程序,并通過正常的驅動程序模型綁定到spi_device。
SPI的I/O模型是一組排隊的消息,在協議驅動程序中可提交一個或多個struct spi_message對象,這些對象被異步處理和完成(包含同步包裝器)。消息是從一個或多個struct spi_transfer對象構建,每個對象都封裝了一個全雙工SPI傳輸,在開發中需要對各種協議進行配置,因為不同的芯片采用不同的策略來使用SPI傳輸的數據。
1、struct spi_statistics
struct spi_statistics描述spi傳輸的統計信息。
該結構中放置了幾個u64_stats_t類型的數據,描述統計了spi傳輸的統計信息,該結構實現如下:
structspi_statistics{ structu64_stats_syncsyncp;//該參數用于保護這個結構體中的成員,在32位系統上實現per-cpu更新。 u64_stats_tmessages;//處理的spi消息數。 u64_stats_ttransfers;//處理的spi_transfer的數量。 u64_stats_terrors;//在spi_transfer過程中的錯誤數。 u64_stats_ttimedout;//spi_transfer期間的timeout。 u64_stats_tspi_sync;//使用spi_sync的次數。 u64_stats_tspi_sync_immediate;//立即執行spi_sync的次數(在調用上下文時不需要排隊和調度)。 u64_stats_tspi_async;//使用spi_async的次數。 u64_stats_tbytes;//傳輸到/從設備接收的字節數。 u64_stats_tbytes_rx;//從設備接收的字節數。 u64_stats_tbytes_tx;//發送到設備的字節數。 #defineSPI_STATISTICS_HISTO_SIZE17 u64_stats_ttransfer_bytes_histo[SPI_STATISTICS_HISTO_SIZE];//用于描述直方圖的數據數組。 u64_stats_ttransfers_split_maxsize;//傳輸數最大尺寸限制。 };
2、struct spi_delay
struct spi_delay用于描述SPI延時信息。
在linux內核中有特定的延時方法,但是spi框架基于udelay()實現了自己的延時,這個延時用于spi的數據傳輸,struct spi_delay實現如下:
structspi_delay{ #defineSPI_DELAY_UNIT_USECS0 #defineSPI_DELAY_UNIT_NSECS1 #defineSPI_DELAY_UNIT_SCK2 u16value;//延時的值。 u8unit;//延時的單位。 };
3、struct spi_device
struct spi_device用于描述控制器端SPI從設備。
該數據結構為linux內核spi子系統的內部結構。
struct spi_device定義如下:
structspi_device{ structdevicedev;//設備的驅動模型表示。 structspi_controller*controller;//與設備配套使用的SPI控制器。 structspi_controller*master;//控制器的副本(用于為了實現向后兼容)。 u32max_speed_hz;//此芯片(在此板上)使用的最大時鐘速率;可能由設備的驅動程序更改。```spi_transfer.speed_hz```可以在每次傳輸時覆蓋此設置。 u8chip_select;//芯片選擇,用于區分由控制器處理的芯片。 u8bits_per_word;//表示數據傳輸涉及的字長,例如8位或12位這樣的字長很常見。可以在每次傳輸spi_transfer.bits_per_word時重寫此設置。 boolrt;//該參數用于開始是否開啟實時線程優先特性。 #defineSPI_NO_TXBIT(31); #defineSPI_NO_RXBIT(30); #defineSPI_TPM_HW_FLOWBIT(29); #defineSPI_MODE_KERNEL_MASK(~(BIT(29)-1)); u32mode; intirq;//該參數可能為負值,或者傳遞給request_irq()以接收來自該設備的中斷的數字。 void*controller_state;//控制器的運行狀態。 void*controller_data;//特定于主板的控制器定義,例如FIFO初始化參數;來自于board_info.controller_data。 charmodalias[SPI_NAME_SIZE];//要與此設備一起使用的驅動程序名稱,或該名稱的別名。這出現在用于驅動冷插拔的sysfs的“modalias”屬性中,以及用于熱插拔的事件中。 constchar*driver_override;//如果將驅動程序的名稱寫入此屬性,則設備將綁定到命名的驅動程序,并且僅綁定到命名的驅動程序。 structgpio_desc*cs_gpiod;//芯片選擇線(CS)的GPIO描述符(該參數可選,如果不使用GPIO line該參數為NULL)。 structspi_delayword_delay;//表示在傳送的連續字(Word)之間插入的延遲。 structspi_delaycs_setup;//表示在CS被斷言后由控制器引入的延遲。 structspi_delaycs_hold;//表示控制器在CS解除斷言之前引入的延遲。 structspi_delaycs_inactive;//CS解除斷言后控制器引入的延時。如果在spi_transfer中使用cs_change_delay,則兩個延遲將相加。 structspi_statistics__percpu*pcpu_statistics;//表示spi_device的統計信息。 };
4、struct spi_driver
struct spi_driver用于描述主機端“協議”驅動程序。
為什么叫“協議”驅動程序,實屬不易理解,這個詞我是從官方文檔中的描述(“protocol”)直接音譯過來,因為這個結構主要用于基于spi總線協議通信的從設備。
struct spi_driver結構實現如下:
structspi_driver{ conststructspi_device_id*id_table;//描述這個驅動程序支持的SPI設備列表。 int(*probe)(structspi_device*spi);//用于將此驅動程序綁定到SPI設備。驅動程序可以驗證設備是否實際存在,可能需要配置不需要的特征(例如bits_per_word)。用于系統啟動過程中完成初始配置。 void(*remove)(structspi_device*spi);//從SPI設備解除與這個驅動程序的綁定。 void(*shutdown)(structspi_device*spi);//在系統狀態轉換期間使用的標準關機回調,如powerdown/halt和kexec。 structdevice_driverdriver;//SPI設備驅動程序應該初始化此結構的name和owner字段。 };
5、struct spi_controller
struct spi_controller描述到SPI主或從控制器的接口。
每個SPI控制器可以與一個或多個spi_device代表的子設備通信。這些設備通常使用4線spi總線:共享MOSI, MISO和SCK信號,但不共享芯片選擇信號。每個設備可以配置為使用不同的時鐘速率。
SPI控制器的驅動程序通過spi_message事務隊列管理對這些設備的訪問,在CPU內存和SPI從設備之間復制數據。對于它所排隊的每條這樣的消息,在事務完成時將調用spi_message的*complete回調函數。
struct spi_controller實現如下:
structspi_controller{ structdevicedev;//此驅動程序的設備接口。 structlist_headlist;//鏈接到全局spi_controller列表。 s16bus_num;//給定SPI控制器的特定板級標識符。 u16num_chipselect;//chipselects用于區分各個SPI從機,編號從0到num_chipselects。每個從機都有一個芯片選擇信號。 u16dma_alignment;//SPI控制器對DMA緩沖區對齊的約束。 u32mode_bits;//由控制器驅動程序解析的標志。 u32buswidth_override_bits;//要覆蓋此控制器驅動程序的標志 u32bits_per_word_mask;//一個掩碼參數,指示驅動程序支持bits_per_word的哪些值,第n位表示支持的bits_per_word為n+1 #defineSPI_BPW_MASK(bits)BIT((bits)-1); #defineSPI_BPW_RANGE_MASK(min,max)GENMASK((max)-1,(min)-1); u32min_speed_hz;//最低支持的傳輸速度。 u32max_speed_hz;//最高支持的傳輸速度 u16flags;//與此驅動程序相關的其他約束標志 #defineSPI_CONTROLLER_HALF_DUPLEXBIT(0); #defineSPI_CONTROLLER_NO_RXBIT(1); #defineSPI_CONTROLLER_NO_TXBIT(2); #defineSPI_CONTROLLER_MUST_RXBIT(3); #defineSPI_CONTROLLER_MUST_TXBIT(4); #defineSPI_CONTROLLER_GPIO_SSBIT(5); booldevm_allocated;//該結構體的分配是否為線程管理 union{ boolslave;//表示這是一個SPI從控制器 booltarget;//表示這是一個SPI目標控制器 }; size_t(*max_transfer_size)(structspi_device*spi);//返回spi_device的最大傳輸大小的回調函數;該回調函數指針可能為NULL,這時候將使用默認的SIZE_MAX。 size_t(*max_message_size)(structspi_device*spi);//返回spi_device的最大消息大小的回函數調;該回調函數可能為NULL,這時候將使用默認的SIZE_MAX。 structmutexio_mutex;//用于物理總線訪問的互斥鎖。 structmutexadd_lock;//該互斥鎖用于避免將設備添加到相同的芯片選擇。 spinlock_tbus_lock_spinlock;//用于SPI總線鎖定的自旋鎖。 structmutexbus_lock_mutex;//該互斥鎖用于排除多個調用者。 boolbus_lock_flag;//表示SPI總線為獨占使用而被鎖定。 int(*setup)(structspi_device*spi);//更新設備的SPI控制器使用的設備模式和時鐘記錄;協議代碼可以調用這個。如果請求無法識別或不支持的模式,則此操作必須失敗。 int(*set_cs_timing)(structspi_device*spi);//SPI設備請求SPI主控制器配置特定的CS設置時間,保持時間和非活動延遲的時鐘計數的回調函數。該函數可選。 int(*transfer)(structspi_device*spi,structspi_message*mesg);//將消息添加到控制器的傳輸隊列。 void(*cleanup)(structspi_device*spi);//釋放特定于控制器的狀態 bool(*can_dma)(structspi_controller*ctlr,structspi_device*spi,structspi_transfer*xfer);//判斷該控制器是否支持DMA structdevice*dma_map_dev;//可以用于DMA映射的設備。 structdevice*cur_rx_dma_dev;//當前用于RX DMA映射的設備。 structdevice*cur_tx_dma_dev;//當前用于TX DMA映射的設備。 boolqueued;//此控制器是否提供內部消息隊列。 structkthread_worker*kworker;//指向消息pump的線程結構的指針。 structkthread_workpump_messages;//將工作安排到消息pump的工作結構。 spinlock_tqueue_lock;//該自旋鎖用于同步對消息隊列的訪問。 structlist_headqueue;//消息隊列。 structspi_message*cur_msg;//當前正在傳輸的消息。 structcompletioncur_msg_completion;//完成當前正在運行的消息。 boolcur_msg_incomplete;//內部使用標志,用于跳過cur_msg_completion。此標志用于檢查驅動程序是否已經調用了spi_finalize_current_message()。 boolcur_msg_need_completion;//內部使用標志,用于跳過cur_msg_completion。此標志用于通知正在運行spi_finalize_current_message()的上下文,它需要complete() boolbusy;//用于描述消息pump是否busy。 boolrunning;//用于描述消息pump是否running。 boolrt;//是否將此隊列設置為作為實時任務運行。 boolauto_runtime_pm;//該標志用于描述是否內核應該確保在硬件準備好時保持運行時PM的引用,使用父設備進行擴展。 boolcur_msg_mapped;//用于描述消息是否已映射為DMA。 charlast_cs;//表示set_cs記錄的最后一個chip_select,在非芯片選擇時值為-1。 boollast_cs_mode_high; boolfallback; structcompletionxfer_completion;//該參數由transfer_one_message()使用 size_tmax_dma_len;//設備DMA傳輸的最大長度。 int(*prepare_transfer_hardware)(structspi_controller*ctlr);//spi子系統請求驅動程序通過發出此調用來準備傳輸硬件 int(*transfer_one_message)(structspi_controller*ctlr,structspi_message*mesg);//spi子系統調用驅動程序來傳輸單個消息,同時對到達的傳輸進行排隊。 當驅動程序處理完這個消息后,必須調用spi_finalize_current_message(),這樣spi子系統才能發出下一個消息。 int(*unprepare_transfer_hardware)(structspi_controller*ctlr);//當前隊列上沒有更多的消息時,spi子系統將通知驅動程序,spi子系統通過調用該回調來釋放硬件。 int(*prepare_message)(structspi_controller*ctlr,structspi_message*message);//該回調用于設置控制器以傳輸單個消息,例如:進行DMA映射,該回調在線程上下文中調用。 int(*unprepare_message)(structspi_controller*ctlr,structspi_message*message);//撤銷prepare_message()所做的所有操作。 union{ int(*slave_abort)(structspi_controller*ctlr);//該回調用于中止SPI從控制器上正在進行的傳輸請求。 int(*target_abort)(structspi_controller*ctlr);//該回調用于中止在SPI目標控制器上正在進行的傳輸請求 }; void(*set_cs)(structspi_device*spi,boolenable);//設置芯片選擇線(CS)的邏輯電平。可以從中斷上下文調用。 //傳輸單個spi_transfer。如果傳輸完成,返回0;如果傳輸仍在進行中,返回1。當驅動程序完成此傳輸時,它必須調用spi_finalize_current_transfer(),以便子系統可以發出下一次傳輸。 int(*transfer_one)(structspi_controller*ctlr,structspi_device*spi,structspi_transfer*transfer); //spi子系統調用驅動程序中的該函數來處理在transfer_one_message()中發生的錯誤。 void(*handle_err)(structspi_controller*ctlr,structspi_message*message); conststructspi_controller_mem_ops*mem_ops;//與SPI內存交互的優化/專用操作。該字段可選,只有在控制器具有內存類操作的原生支持時才應該實現。 conststructspi_controller_mem_caps*mem_caps;//處理內存操作的控制器能力。 structgpio_desc**cs_gpiods;//用作芯片選擇線的GPIO描述符數組;每個CS號碼一個。對于非gpio(由SPI控制器本身驅動)的CS線,任何單個值都可能為NULL。 booluse_gpio_descriptors;//是否打開SPI核心中的代碼來解析和獲取GPIO描述符,此后將填充cs_gpiods,如果為芯片選擇找到了GPIO線,SPI設備將分配cs_gpiods。 s8unused_native_cs;//當使用cs_gpiods時,spi_register_controller()將用第一個未使用的原生CS填充此字段,供在使用GPIO CS時需要驅動原生CS的SPI控制器驅動程序使用。 s8max_native_cs;//當使用cs_gpiods并填充此字段時,spi_register_controller()將根據此值驗證所有原生CS(包括未使用的原生CS)。 structspi_statistics__percpu*pcpu_statistics;//為spi_controller的統計。 structdma_chan*dma_tx;//DMA傳輸通道。 structdma_chan*dma_rx;//DMA接收通道。 void*dummy_rx;//全雙工設備的虛擬接收緩沖區 void*dummy_tx;//全雙工設備的虛擬傳輸緩沖器 int(*fw_translate_cs)(structspi_controller*ctlr,unsignedcs);//如果引導固件使用與Linux期望編號方案不同,則可以使用這個可選回調在二者之間進行轉換。 boolptp_sts_supported;//如果驅動程序將其設置為true,則驅動程序必須在spi_transfer->ptp_sts中提供一個時間快照,盡可能接近spi_transfer->ptp_sts_word_pre和spi_transfer->ptp_sts_word_post傳輸的時刻。如果驅動程序沒有設置這個參數,SPI核心將盡可能接近驅動程序移交的快照。 unsignedlongirq_flags;//表示PTP系統時間戳期間中斷使能狀態。 boolqueue_empty;//該參數用于表示消息隊列是否為空。 boolmust_async;//該參數用于表示是否關閉spi框架的所有快速路徑操作。 };
struct spi_controller中的組成元素算比較多的了。
備注:在舊版的linux內核中使用struct spi_master描述SPI控制器。在較新的linux內核版本中,使用struct spi_controller替換了struct spi_master。
6、struct spi_res
struct spi_res用于描述資源管理結構,側重于在spi_message處理期間的生命周期管理。該結構定義如下:
structspi_res{ structlist_headentry;//資源鏈表項 spi_res_release_trelease;//釋放此資源之前調用的釋放代碼 unsignedlonglongdata[];//為特定用例分配的額外數據 };
7、struct spi_transfer
struct spi_transfer用于描述讀/寫緩沖對,該結構定義定義如下:
structspi_transfer{ constvoid*tx_buf;//要寫入的數據(DMA安全內存),該值可能為NULL。 void*rx_buf;//要讀取的數據(DMA安全內存),該值可能為NULL unsignedlen;//rx和tx緩沖區的大小(字節為單位) #defineSPI_TRANS_FAIL_NO_STARTBIT(0); u16error;//SPI控制器驅動程序記錄的錯誤狀態。 dma_addr_ttx_dma;//tx_buf的DMA地址,如spi_message.is_dma_mapped。 dma_addr_trx_dma;//rx_buf的DMA地址,如spi_message.is_dma_mapped。 structsg_tabletx_sg;//用于傳輸的散列表。 structsg_tablerx_sg;//用于接收的散列表; unsigneddummy_data:1; unsignedcs_off:1; unsignedcs_change:1; unsignedtx_nbits:3;//用于寫入的位數。如果為0,則使用默認值(SPI_NBITS_SINGLE)。 unsignedrx_nbits:3;//用于讀取的位數。如果為0,則使用默認值(SPI_NBITS_SINGLE)。 unsignedtimestamped:1;//如果傳輸有時間戳,則為True #defineSPI_NBITS_SINGLE0x01; #defineSPI_NBITS_DUAL0x02; #defineSPI_NBITS_QUAD0x04; u8bits_per_word;//為此傳輸選擇一個bits_per_word,而不是設備默認值。如果為0,則使用默認值(來自spi_device)。 structspi_delaydelay;//在此傳輸之后(可選地)更改chipselect狀態之前引入的延遲,然后開始下一次傳輸或完成此spi_message。 structspi_delaycs_change_delay;//當設置了cs_change并且spi_transfer不是spi_message中的最后一個時,在cs deassert和assert之間的延遲。 structspi_delayword_delay;//每個字長(由bits_per_word設置)傳輸后引入的字間延遲。 u32speed_hz;//為此傳輸選擇設備默認速度以外的速度。如果為0,則使用默認值(來自spi_device)。 u32effective_speed_hz;//用于傳輸此傳輸的有效sck速度。如果SPI總線驅動不支持,設置為0。 unsignedintptp_sts_word_pre;//在tx_buf中的字(受bits_per_word語義約束)偏移量,SPI設備為此請求開始此傳輸的時間快照。在完成SPI傳輸后,該值可能與請求的值相比發生了變化,這取決于可用的快照分辨率(DMA傳輸、ptp_sts_supported為false等)。 unsignedintptp_sts_word_post; structptp_system_timestamp*ptp_sts; structlist_headtransfer_list;//通過spi_message.transfers進行排序的transfer。 };
SPI傳輸總是需要寫入與讀取相同數量的字節。struct spi_driverSPI設備驅動程序應該提供rx_buf或tx_buf。在某些情況下,可能還需要為正在傳輸的數據提供DMA地址,當底層驅動程序使用DMA時,這樣會減少CPU開銷。
如果傳輸緩沖區為NULL,則在填充rx_buf時將移出零。如果接收緩沖區為NULL,則移入的數據將被丟棄。只有len字節移出(或移進)。
內存中的數據總是按照本地CPU字節順序進行排列。例如:當bits_per_word為16時,緩沖區是2N字節長(len = 2N),并按CPU字節順序保存N個16位字。
當SPI傳輸的字長不是8的2次冪倍數時,內存中的字包含額外的位。內存中的字總是被協議驅動程序視為右對齊,因此未定義(rx)或未使用(tx)位始終是最重要的位。
所有SPI傳輸從芯片選擇(CS)被激活開始。通常,直到消息中的最后一次傳輸之后,它才會被選中。驅動程序可以使用cs_change影響芯片選擇(CS)信號:
(1)如果傳輸不是消息中的最后一個,則此標志用于使CS在消息中間短暫地處于非活動狀態。以這種方式切換CS可能需要終止一個芯片命令,讓單個spi_message一起執行所有的芯片事務組。
(2)當傳輸是消息中的最后一個傳輸時,芯片可以保持選中狀態直到下一次傳輸。在多設備SPI總線上,沒有阻止消息到其他設備,這只是一個性能提示;向另一個設備發送消息將取消選中該設備。但在其他情況下,這可以用來確保正確性,一些SPI設備需要通過一系列spi_message提交來構建協議事務,其中一條消息的內容由之前消息的結果決定,當CS處于非活動狀態時,整個事務結束。
當SPI可以在1x,2x或4x傳輸時。它可以通過tx_nbits和rx_nbits從設備獲取傳輸信息。在雙向傳輸中,tx_nbits和rx_nbits都應該被設置。用戶可以設置傳輸模式SPI_NBITS_SINGLE(1x)、SPI_NBITS_DUAL(2x)和SPI_NBITS_QUAD(4x)來支持這三種傳輸方式。
將spi_message(及其spi_transfers)提交給較低層的代碼負責管理其內存。因此零初始化沒有顯式設置的字段,可以防止未來API更新。在提交消息及其傳輸之后,忽略它們,直到它完成回調。
8、struct spi_message
spi_message用于執行數據傳輸的原子序列,每個序列由struct spi_transfer表示。該結構定義如下:
structspi_message{ structlist_headtransfers;//該傳輸中傳輸段鏈表 structspi_device*spi;//表示該傳輸的SPI設備 unsignedis_dma_mapped:1;//如果為true,則調用者為每個傳輸緩沖區提供DMA和CPU虛擬地址。 boolprepared;//是否為此消息調用spi_prepare_message() intstatus;//表示傳輸狀態,0表示成功,否則為負errno void(*complete)(void*context);//調用該回調以報告事務的完成情況。 void*context;//調用complete()時的參數。 unsignedframe_length;//message中的總字節數。 unsignedactual_length;//傳輸成功的字節總數。 structlist_headqueue;//該參數供當前擁有該消息的驅動程序使用。 void*state;//該參數供當前擁有該消息的驅動程序使用。 structlist_headresources;//用于處理SPI消息時的資源管理。 structspi_transfert[];//該組成元素用于spi_message_alloc()。(當消息和傳輸已經一起分配時) };
一個spi_message用于執行數據傳輸的原子序列,每個序列由struct spi_transfer結構表示。該序列是“原子的”,因為在該序列完成之前,沒有其他spi_message可以使用該SPI總線。在一些系統中,許多這樣的序列可以作為單個編程的DMA傳輸來執行。在所有系統上,這些消息都是以隊列方式組織的,并且可能在發送到其他設備的事務之后完成,發送到給定spi_device的消息總是按照FIFO順序執行。
將spi_message(及其spi_transfers)提交給較底層的代碼負責管理其內存。使用零初始化沒有顯式設置的每個字段,以隔離后續可能發生的API更新帶來的影響。
9、struct spi_board_info
struct spi_board_info用于SPI設備的特定板卡模板。該結構定義如下:
structspi_board_info{ charmodalias[SPI_NAME_SIZE];//用于初始化spi_device.modalias,用于識別驅動程序。 constvoid*platform_data;//用于初始化spi_device.platform_data,用于存儲特定數據。 conststructsoftware_node*swnode;//用于描述設備的軟件節點 void*controller_data;//用于初始化spi_device.controller_data;一些控制器需要提示硬件設置,例如DMA。 intirq;//用于初始化spi_device.irq;取決于板卡的連接。 u32max_speed_hz;//用于初始化spi_device.max_speed_hz;基于芯片數據表和主板特定信號質量問題的限制。 u16bus_num;//識別哪些spi_controller作為spi_device的父設備;在spi_new_device()中未使用,取決于板卡接線。 u16chip_select;//用于初始化spi_device.chip_select;取決于板卡連接。 u32mode;//用于初始化spi_device.mode;根據芯片數據表,電路板布線。 };
當向設備樹中添加新的SPI設備時,該結構可用作設備模板,該結構在兩個地方使用,第一個作用是可存儲在板卡特定設備描述符的表中,這些描述符在板卡初始化的早期聲明,然后在控制器的驅動程序初始化之后使用。第二個作用是作為spi_new_device()調用的參數。
四、SPI框架的常用API總結
linux內核不同版本的SPI框架開放的API可能不同,以具體源碼為主!
//1、初始化spi_message并附加到transfer voidspi_message_init_with_transfers(structspi_message*m,structspi_transfer*xfers,unsignedintnum_xfers) //2、檢查是否支持每字位 boolspi_is_bpw_supported(structspi_device*spi,u32bpw) //3、計算一個合適的超時值 unsignedintspi_controller_xfer_timeout(structspi_controller*ctlr,structspi_transfer*xfer) //4、同步SPI數據傳輸 intspi_sync_transfer(structspi_device*spi,structspi_transfer*xfers,unsignedintnum_xfers) //5、SPI同步寫操作 intspi_write(structspi_device*spi,constvoid*buf,size_tlen) //6、SPI同步讀操作 intspi_read(structspi_device*spi,void*buf,size_tlen) //7、SPI同步8位寫然后8位讀 ssize_tspi_w8r8(structspi_device*spi,u8cmd) //8、SPI同步8位寫然后16位讀 ssize_tspi_w8r16(structspi_device*spi,u8cmd) //9、SPI同步8位寫入,然后16位大端讀 ssize_tspi_w8r16be(structspi_device*spi,u8cmd) //10、為給定的board注冊SPI設備 intspi_register_board_info(structspi_board_infoconst*info,unsignedn) //11、注冊一個SPI驅動 int__spi_register_driver(structmodule*owner,structspi_driver*sdrv) //12、分配新的SPI設備 structspi_device*spi_alloc_device(structspi_controller*ctlr) //13、向SPI核心添加使用spi_alloc_device分配的spi_device intspi_add_device(structspi_device*spi) //14、實例化一個新的SPI設備 structspi_device*spi_new_device(structspi_controller*ctlr,structspi_board_info*chip) //15、注銷單個SPI設備 voidspi_unregister_device(structspi_device*spi) //16、報告transfer的完成情況 voidspi_finalize_current_transfer(structspi_controller*ctlr) //17、獲取TX開始時間戳的助手函數 voidspi_take_timestamp_pre(structspi_controller*ctlr,structspi_transfer*xfer,size_tprogress,boolirqs_off) //18、獲取TX結束時間戳的助手函數 voidspi_take_timestamp_post(structspi_controller*ctlr,structspi_transfer*xfer,size_tprogress,boolirqs_off) //19、獲取下一個排隊的消息。(由驅動程序調用用于檢查排隊的消息) structspi_message*spi_get_next_queued_message(structspi_controller*ctlr) //20、由驅動程序調用,通知內核隊列前面的消息已經完成,可以從隊列中刪除。 voidspi_finalize_current_message(structspi_controller*ctlr) //21、注冊輔助SPI設備(該函數只能從主SPI設備的probe函數中調用) structspi_device*spi_new_ancillary_device(structspi_device*spi,u8chip_select) //22、統計SpiSerialBus資源的個數 intacpi_spi_count_resources(structacpi_device*adev) //23、中止SPI從控制器上正在進行的傳輸請求 intspi_slave_abort(structspi_device*spi) //24、分配一個SPI主控制器或者從控制器 structspi_controller*__spi_alloc_controller(structdevice*dev,unsignedintsize,boolslave) //25、帶資源管理的__spi_alloc_controller() structspi_controller*__devm_spi_alloc_controller(structdevice*dev,unsignedintsize,boolslave) //26、注冊SPI主控制器或者從控制器 intspi_register_controller(structspi_controller*ctlr) //27、帶資源管理的spi_register_controller() intdevm_spi_register_controller(structdevice*dev,structspi_controller*ctlr) //28、注銷SPI主控制器或從控制器 voidspi_unregister_controller(structspi_controller*ctlr) //29、當單個傳輸超過一定大小時,將spi傳輸拆分為多個傳輸 intspi_split_transfers_maxsize(structspi_controller*ctlr,structspi_message*msg,size_tmaxsize,gfp_tgfp) //30、當單個傳輸超過一定數量的SPI字時,將SPI傳輸拆分為多個傳輸 intspi_split_transfers_maxwords(structspi_controller*ctlr,structspi_message*msg,size_tmaxwords,gfp_tgfp) //31、設置SPI模式和時鐘速率 intspi_setup(structspi_device*spi) //32、異步SPI傳輸 intspi_async(structspi_device*spi,structspi_message*message) //33、阻塞/同步SPI數據傳輸 intspi_sync(structspi_device*spi,structspi_message*message) //34、具有獨占總線使用的spi_sync()版本 intspi_sync_locked(structspi_device*spi,structspi_message*message) //35、獲得獨占SPI總線使用的鎖 intspi_bus_lock(structspi_controller*ctlr) //36、釋放獨占SPI總線使用的鎖 intspi_bus_unlock(structspi_controller*ctlr) //37、SPI同步寫然后讀。 intspi_write_then_read(structspi_device*spi,constvoid*txbuf,unsignedn_tx,void*rxbuf,unsignedn_rx)
五、SPI驅動實例分析
SPI驅動分為兩個部分:主機側驅動和設備側驅動。
(5-1)SPI主機側驅動
(1)SPI主機側驅動設計思路
一般情況下,SPI主機側的驅動程序芯片原廠,會去實現,并會合并到自己廠家維護的linux內核版本中發布給其他基于該芯片設計的廠商。在實現SPi主機側驅動的時候,可以基于平臺設備驅動框架實現,然后使用module_platform_driver()或者其他模塊函數導出,例如:module_init()。在平臺驅動的.probe指向的函數中實現spi驅動:
1、區分spi驅動是slave還是master,并創建對應的struct spi_controller,如果是slave,則使用spi_alloc_slave()創建,如果是master,則使用spi_allov_master()創建。
2、實現spi寄存器相關的映射。
3、設置spi時鐘。
4、創建spi中斷服務函數(以中斷線程化方式實現)。
5、初始化spi_controller相關組成元素的信息。
6、指定struct spi_controller操作的callback。
7、spi控制器相關的狀態獲取和保存。
8、注冊spi控制器。可使用devm_spi_register_controller()或者相關接口實現。
(2)、SPI主機側驅動案例分析
本小節,以Rockchip的rk3568的SPI主機側驅動為例。分析SPI主機側驅動的實現步驟,rk3568的spi驅動位于/drivers/spi/spi-rockchip.c(以具體linux內核源碼為準)文件中。該驅動以platform驅動框架為基礎實現,對應的struct platform_driver實現如下:
staticstructplatform_driverrockchip_spi_driver={ .driver={ .name=DRIVER_NAME, .pm=&rockchip_spi_pm, .of_match_table=of_match_ptr(rockchip_spi_dt_match), }, .probe=rockchip_spi_probe, .remove=rockchip_spi_remove, }; module_platform_driver(rockchip_spi_driver);
在源碼的最后使用module_platform_driver()導出spi驅動。
接著看看rockchip_spi_dt_match設備匹配表,定義如下:
可見該spi驅動支持的芯片類型比較多。
再看看.probe對應的rockchip_spi_probe(),該函數實現如下(函數中內容較多):
staticintrockchip_spi_probe(structplatform_device*pdev) { intret; structrockchip_spi*rs; structspi_controller*ctlr; structresource*mem; structdevice_node*np=pdev->dev.of_node; u32rsd_nsecs; boolslave_mode; structpinctrl*pinctrl=NULL; slave_mode=of_property_read_bool(np,"spi-slave"); if(slave_mode) ctlr=spi_alloc_slave(&pdev->dev, sizeof(structrockchip_spi)); else ctlr=spi_alloc_master(&pdev->dev, sizeof(structrockchip_spi)); if(!ctlr) return-ENOMEM; platform_set_drvdata(pdev,ctlr); rs=spi_controller_get_devdata(ctlr); ctlr->slave=slave_mode; /*Getbasicioresourceandmapit*/ mem=platform_get_resource(pdev,IORESOURCE_MEM,0); rs->regs=devm_ioremap_resource(&pdev->dev,mem); if(IS_ERR(rs->regs)){ ret=PTR_ERR(rs->regs); gotoerr_put_ctlr; } rs->apb_pclk=devm_clk_get(&pdev->dev,"apb_pclk"); if(IS_ERR(rs->apb_pclk)){ dev_err(&pdev->dev,"Failedtogetapb_pclk "); ret=PTR_ERR(rs->apb_pclk); gotoerr_put_ctlr; } rs->spiclk=devm_clk_get(&pdev->dev,"spiclk"); if(IS_ERR(rs->spiclk)){ dev_err(&pdev->dev,"Failedtogetspi_pclk "); ret=PTR_ERR(rs->spiclk); gotoerr_put_ctlr; } ret=clk_prepare_enable(rs->apb_pclk); if(ret0)?{ ??dev_err(&pdev->dev,"Failedtoenableapb_pclk "); gotoerr_put_ctlr; } ret=clk_prepare_enable(rs->spiclk); if(ret0)?{ ??dev_err(&pdev->dev,"Failedtoenablespi_clk "); gotoerr_disable_apbclk; } spi_enable_chip(rs,false); ret=platform_get_irq(pdev,0); if(ret0) ??goto?err_disable_spiclk; ?ret?=?devm_request_threaded_irq(&pdev->dev,ret,rockchip_spi_isr,NULL, IRQF_ONESHOT,dev_name(&pdev->dev),ctlr); if(ret) gotoerr_disable_spiclk; rs->dev=&pdev->dev; rs->freq=clk_get_rate(rs->spiclk); rs->gpio_requested=false; if(!of_property_read_u32(pdev->dev.of_node,"rx-sample-delay-ns", &rsd_nsecs)){ /*rxsampledelayisexpressedinparentclockcycles(max3)*/ u32rsd=DIV_ROUND_CLOSEST(rsd_nsecs*(rs->freq>>8), 1000000000>>8); if(!rsd){ dev_warn(rs->dev,"%uHzaretooslowtoexpress%unsdelay ", rs->freq,rsd_nsecs); }elseif(rsd>CR0_RSD_MAX){ rsd=CR0_RSD_MAX; dev_warn(rs->dev,"%uHzaretoofasttoexpress%unsdelay,clampingat%uns ", rs->freq,rsd_nsecs, CR0_RSD_MAX*1000000000U/rs->freq); } rs->rsd=rsd; } rs->fifo_len=get_fifo_len(rs); if(!rs->fifo_len){ dev_err(&pdev->dev,"Failedtogetfifolength "); ret=-EINVAL; gotoerr_disable_spiclk; } pm_runtime_set_active(&pdev->dev); pm_runtime_enable(&pdev->dev); ctlr->auto_runtime_pm=true; ctlr->bus_num=pdev->id; ctlr->mode_bits=SPI_CPOL|SPI_CPHA|SPI_LOOP|SPI_LSB_FIRST|SPI_CS_HIGH; if(slave_mode){ ctlr->mode_bits|=SPI_NO_CS; ctlr->slave_abort=rockchip_spi_slave_abort; }else{ ctlr->flags=SPI_MASTER_GPIO_SS; } ctlr->num_chipselect=ROCKCHIP_SPI_MAX_CS_NUM; ctlr->dev.of_node=pdev->dev.of_node; ctlr->bits_per_word_mask=SPI_BPW_MASK(16)|SPI_BPW_MASK(8)|SPI_BPW_MASK(4); ctlr->min_speed_hz=rs->freq/BAUDR_SCKDV_MAX; ctlr->max_speed_hz=min(rs->freq/BAUDR_SCKDV_MIN,MAX_SCLK_OUT); ctlr->set_cs=rockchip_spi_set_cs; ctlr->setup=rockchip_spi_setup; ctlr->cleanup=rockchip_spi_cleanup; ctlr->transfer_one=rockchip_spi_transfer_one; ctlr->max_transfer_size=rockchip_spi_max_transfer_size; ctlr->handle_err=rockchip_spi_handle_err; ctlr->dma_tx=dma_request_chan(rs->dev,"tx"); if(IS_ERR(ctlr->dma_tx)){ /*Checktxtoseeifweneeddeferprobingdriver*/ if(PTR_ERR(ctlr->dma_tx)==-EPROBE_DEFER){ ret=-EPROBE_DEFER; gotoerr_disable_pm_runtime; } dev_warn(rs->dev,"FailedtorequestTXDMAchannel "); ctlr->dma_tx=NULL; } ctlr->dma_rx=dma_request_chan(rs->dev,"rx"); if(IS_ERR(ctlr->dma_rx)){ if(PTR_ERR(ctlr->dma_rx)==-EPROBE_DEFER){ ret=-EPROBE_DEFER; gotoerr_free_dma_tx; } dev_warn(rs->dev,"FailedtorequestRXDMAchannel "); ctlr->dma_rx=NULL; } if(ctlr->dma_tx&&ctlr->dma_rx){ rs->dma_addr_tx=mem->start+ROCKCHIP_SPI_TXDR; rs->dma_addr_rx=mem->start+ROCKCHIP_SPI_RXDR; ctlr->can_dma=rockchip_spi_can_dma; } switch(readl_relaxed(rs->regs+ROCKCHIP_SPI_VERSION)){ caseROCKCHIP_SPI_VER2_TYPE1: caseROCKCHIP_SPI_VER2_TYPE2: if(ctlr->can_dma&&slave_mode) rs->cs_inactive=true; else rs->cs_inactive=false; break; default: rs->cs_inactive=false; } pinctrl=devm_pinctrl_get(&pdev->dev); if(!IS_ERR(pinctrl)){ rs->high_speed_state=pinctrl_lookup_state(pinctrl,"high_speed"); if(IS_ERR_OR_NULL(rs->high_speed_state)){ dev_warn(&pdev->dev,"nohigh_speedpinctrlstate "); rs->high_speed_state=NULL; } } ret=devm_spi_register_controller(&pdev->dev,ctlr); if(ret0)?{ ??dev_err(&pdev->dev,"Failedtoregistercontroller "); gotoerr_free_dma_rx; } return0; err_free_dma_rx: if(ctlr->dma_rx) dma_release_channel(ctlr->dma_rx); err_free_dma_tx: if(ctlr->dma_tx) dma_release_channel(ctlr->dma_tx); err_disable_pm_runtime: pm_runtime_disable(&pdev->dev); err_disable_spiclk: clk_disable_unprepare(rs->spiclk); err_disable_apbclk: clk_disable_unprepare(rs->apb_pclk); err_put_ctlr: spi_controller_put(ctlr); returnret; }
上述.probe實現的主要步驟如下:
讀取spi-slave屬性獲取模式,如果是slave模式,則調用spi_alloc_slave()分配struct spi_contoller內存,否則為master模式,則調用spi_alloc_master()同樣分配一個struct spi_contoller內存。
獲取基本的IO資源并對其進行映射。
獲取時鐘并enable時鐘。
調用platform_get_irq()獲取中斷號,接著調用devm_request_threaded_irq()創建中斷處理函數,其中中斷處理函數為rockchip_spi_isr()。
設置struct rockchip_spi結構中的組成元素。struct rockchip_spi表示具體的spi控制器。
設置struct spi_controller 結構中的組成元素。
最后調用devm_spi_register_controller()注冊spi控制器。
(5-2)SPI設備側驅動
(1)SPI設備側驅動設計思路
對于SPI設備的驅動,主要圍繞如何與該SPI設備進行數據通信或者實現控制。在SPI控制器驅動實現的情況下,SPI設備側的實現思路:
1、對SPI設備進行描述。
可以通過修改設備樹的方式對SPI設備進行描述。
2、創建struct spi_driver的具體實例作為設備側驅動。
3、SPI設備數據收發處理流程
SPI設備數據的收發主要涉及到兩個數據結構:struct spi_message、struct spi_transfer,還需要幾個用于傳輸的API:
//在使用spi_message之前需要對其進行初始化: voidspi_message_init(structspi_message*m) //spi_message初始化完成以后可使用spi_message_add_tail將spi_transfer添加到spi_message隊列中: voidspi_message_add_tail(structspi_transfer*t,structspi_message*m) //spi_message準備好以后既可以進行數據傳輸了,數據傳輸分為同步傳輸和異步傳輸,同步 //傳輸會阻塞的等待SPI數據傳輸完成,同步傳輸函數為spi_sync(): intspi_sync(structspi_device*spi,structspi_message*message) //異步傳輸不會阻塞的等到SPI數據傳輸完成,異步傳輸需要設置spi_message中的complete成員變量,complete是一個回調函數,當SPI異步傳輸完成以后此函數就會被調用。SPI異步傳 //輸函數為spi_async(): intspi_async(structspi_device*spi,structspi_message*message)
4、根據具體驅動需求設計struct file_operations對應的ops的callback。
5、在.probe中使用spi_register_driver()向SPI核心注冊SPI驅動,以字符設備類方式導出用戶空間SPI設備文件,分配中斷等
6、以驅動框架方式導出。
(2)、SPI設備側驅動案例分析
當主機側的SPI實現后,我們可以快速使用SPI控制器與SPI設備進行通信了。本小節以icm20608這款常見的SPI接口的六軸傳感器為例分析SPI設備側驅動的具體實現步驟。
首先使用struct spi_driver創建spi驅動實例icm20608_driver,指定其中的.probe、.remove、driver參數,如有必要可實現.id_table:
staticstructspi_drivericm20608_driver={ .probe=icm20608_probe, .remove=icm20608_remove, .driver={ .owner=THIS_MODULE, .name="ICM20608", .of_match_table=icm20608_of_match, }, };
接著使用module_init()初始化模塊,在模塊初始化函數中調用spi_register_driver()將icm20608_driver注冊到linux內核。然后實現模塊退出接口函數,在該函數中實現必要的退出清理操作。
接著實現icm20608_probe()、icm20608_remove和設備樹匹配表icm20608_of_match。
在icm20608_probe()中以字符設備方式向用戶暴露出設備文件。并實現對icm20608設備文件的struct file_operations中的callback。例如:.open、.read、release。因icm20608為六軸傳感器,主要操作是讀取數據,所以.write可以不用實現。
在實現.read操作時,主要內容是使用SPI控制器發送相應控制數據到SPI設備。使用SPI控制器發送數據的代碼如下:
inticm20608_readRegs(structicm20608_dev*dev,u8reg,void*buf,intlen) { intret; unsignedchartxdata[len]; structspi_messagemessage; structspi_transfer*transfer; structspi_device*spi=(structspi_device*)dev->private_data; /*1、申請內存*/ transfer=kzalloc(sizeof(structspi_transfer),GFP_KERNEL); /*2、發送要讀取的寄存地址*/ txdata[0]=reg|0x80;/*寫數據的時候寄存器地址bit8要置1*/ transfer->tx_buf=txdata;/*要發送的數據*/ transfer->len=1;/*1個字節*/ /*3、初始化spi_message*/ spi_message_init(&message); /*4、將spi_transfer添加到spi_message隊列*/ spi_message_add_tail(transfer,&message); /*5、同步發送*/ ret=spi_sync(spi,&message); /*6、釋放內存*/ kfree(transfer); returnret; }
六、SPI驅動調試總結
在SPI驅動調試過程中,還是需要注意以下幾點:
(1)確保自己系統的SPI成功運行。這是SPI設備能正常通信工作的前提條件。
(2)在進行SPI數據傳輸時,確認時序是否正確:發送引腳有正常的波形,CLK引腳有正常的時鐘信號,CS引腳有拉低。
(3)確保SPI的4個引腳的引腳復用配置正確。
審核編輯:劉清
-
嵌入式
+關注
關注
5085文章
19140瀏覽量
305786 -
觸摸傳感器
+關注
關注
0文章
122瀏覽量
23072 -
EEPROM
+關注
關注
9文章
1022瀏覽量
81660 -
SPI通信
+關注
關注
0文章
35瀏覽量
11371 -
LINUX內核
+關注
關注
1文章
316瀏覽量
21663
原文標題:linux內核中的spi“簡單”嗎?
文章出處:【微信號:嵌入式小生,微信公眾號:嵌入式小生】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
相關推薦
評論