0
  • 聊天消息
  • 系統(tǒng)消息
  • 評(píng)論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會(huì)員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識(shí)你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

linux內(nèi)核中的SPI框架及SPI核心的初始化簡(jiǎn)析

嵌入式小生 ? 來(lái)源:嵌入式小生 ? 2023-12-11 09:10 ? 次閱讀

一、linux內(nèi)核中的SPI框架

嵌入式linux開(kāi)發(fā)中,SPI是一種常見(jiàn)的通信方式,如下圖所示:

58d7b654-97ba-11ee-8b88-92fbcf53809c.png

常見(jiàn)的屬于SPI設(shè)備包括:RF芯片、智能卡、EEPROM、RTC、觸摸傳感器等等。

在內(nèi)核中,與I2C一樣,也同樣提供了一個(gè)SPI框架,本文圍繞這個(gè)框架展開(kāi),來(lái)分析內(nèi)核提供的SPI框架是如何運(yùn)作的。內(nèi)核中與SPI相關(guān)的代碼規(guī)范放置在/drivers/spi路徑下,

58f01604-97ba-11ee-8b88-92fbcf53809c.png

從Makefile可知,內(nèi)核提供的SPI框架主要實(shí)現(xiàn)在spi.c、spidev.c文件中。

spi.c文件實(shí)現(xiàn)了spi核心的初始化,以及實(shí)現(xiàn)spi框架的相關(guān)API接口。(如果想讓系統(tǒng)支持spi,此文件必須被編譯)

spidev.c文件用于實(shí)現(xiàn)SPI設(shè)備同步用戶空間接口。(該文件為可選特性)

存在/drivers/spi路徑下其他洋洋灑灑的文件則是不同廠家提供的SPI控制器的驅(qū)動(dòng)程序,這些文件往往由芯片廠家開(kāi)發(fā),然后合并到linux內(nèi)核源碼中,以適配自家芯片。

二、SPI核心的初始化

SPI核心的初始化實(shí)現(xiàn)在/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(status

在postcore_initcall()導(dǎo)出的spi核心的初始化過(guò)程中,主要做了以下幾件事情:

1、分配一個(gè)用于SPI的buffer。

2、注冊(cè)spi總線。

3、注冊(cè)spi_master主機(jī)類。

以上操作是內(nèi)核中面向?qū)ο蟮幕A(chǔ)構(gòu)件過(guò)程,spi框架也不例外,也必須這樣實(shí)現(xiàn),以獲得內(nèi)核設(shè)備驅(qū)動(dòng)模型的管理。

關(guān)于spi bus總線對(duì)設(shè)備和驅(qū)動(dòng)的匹配過(guò)程:在spi/spic.c文件中定義了用于描述spi的bus總線,命名為spi,該總線在spi_init()函數(shù)中注冊(cè),匹配過(guò)程由spi_match_device()描述,該函數(shù)實(shí)現(xiàn)如下:

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;
}

在上述代碼中,描述了對(duì)spi設(shè)備和驅(qū)動(dòng)匹配的四種方式。

三、SPI核心的數(shù)據(jù)結(jié)構(gòu)

注意:幾乎芯片原廠都要提供一個(gè)主機(jī)側(cè)的SPI驅(qū)動(dòng),以支持自家的芯片。

編程接口是圍繞兩種驅(qū)動(dòng)程序和兩種設(shè)備構(gòu)建。SPI控制器驅(qū)動(dòng)程序抽象了控制器硬件,它可以像一組GPIO引腳一樣簡(jiǎn)單,也可以像fifo一樣復(fù)雜,也有可能支持DMA引擎(實(shí)現(xiàn)數(shù)據(jù)的最大化吞吐量)。這樣的驅(qū)動(dòng)程序在它們所在的總線(通常是平臺(tái)總線)和SPI之間架橋,并將其設(shè)備的SPI端作為struct spi_controller公開(kāi)。

SPI設(shè)備是主設(shè)備的子設(shè)備,由struct spi_device表示,并由struct spi_board_info描述符進(jìn)行描述,這些描述符通常由特定板卡的初始化代碼提供。

struct spi_driver稱為協(xié)議驅(qū)動(dòng)程序,并通過(guò)正常的驅(qū)動(dòng)程序模型綁定到spi_device。

SPI的I/O模型是一組排隊(duì)的消息,在協(xié)議驅(qū)動(dòng)程序中可提交一個(gè)或多個(gè)struct spi_message對(duì)象,這些對(duì)象被異步處理和完成(包含同步包裝器)。消息是從一個(gè)或多個(gè)struct spi_transfer對(duì)象構(gòu)建,每個(gè)對(duì)象都封裝了一個(gè)全雙工SPI傳輸,在開(kāi)發(fā)中需要對(duì)各種協(xié)議進(jìn)行配置,因?yàn)椴煌男酒捎貌煌牟呗詠?lái)使用SPI傳輸?shù)臄?shù)據(jù)。

1、struct spi_statistics

struct spi_statistics描述spi傳輸?shù)慕y(tǒng)計(jì)信息

該結(jié)構(gòu)中放置了幾個(gè)u64_stats_t類型的數(shù)據(jù),描述統(tǒng)計(jì)了spi傳輸?shù)慕y(tǒng)計(jì)信息,該結(jié)構(gòu)實(shí)現(xiàn)如下:

structspi_statistics{
structu64_stats_syncsyncp;//該參數(shù)用于保護(hù)這個(gè)結(jié)構(gòu)體中的成員,在32位系統(tǒng)上實(shí)現(xiàn)per-cpu更新。

u64_stats_tmessages;//處理的spi消息數(shù)。
u64_stats_ttransfers;//處理的spi_transfer的數(shù)量。
u64_stats_terrors;//在spi_transfer過(guò)程中的錯(cuò)誤數(shù)。
u64_stats_ttimedout;//spi_transfer期間的timeout。

u64_stats_tspi_sync;//使用spi_sync的次數(shù)。
u64_stats_tspi_sync_immediate;//立即執(zhí)行spi_sync的次數(shù)(在調(diào)用上下文時(shí)不需要排隊(duì)和調(diào)度)。
u64_stats_tspi_async;//使用spi_async的次數(shù)。

u64_stats_tbytes;//傳輸?shù)?從設(shè)備接收的字節(jié)數(shù)。
u64_stats_tbytes_rx;//從設(shè)備接收的字節(jié)數(shù)。
u64_stats_tbytes_tx;//發(fā)送到設(shè)備的字節(jié)數(shù)。

#defineSPI_STATISTICS_HISTO_SIZE17
u64_stats_ttransfer_bytes_histo[SPI_STATISTICS_HISTO_SIZE];//用于描述直方圖的數(shù)據(jù)數(shù)組。

u64_stats_ttransfers_split_maxsize;//傳輸數(shù)最大尺寸限制。
};

2、struct spi_delay

struct spi_delay用于描述SPI延時(shí)信息。

在linux內(nèi)核中有特定的延時(shí)方法,但是spi框架基于udelay()實(shí)現(xiàn)了自己的延時(shí),這個(gè)延時(shí)用于spi的數(shù)據(jù)傳輸,struct spi_delay實(shí)現(xiàn)如下:

structspi_delay{
#defineSPI_DELAY_UNIT_USECS0
#defineSPI_DELAY_UNIT_NSECS1
#defineSPI_DELAY_UNIT_SCK2
u16value;//延時(shí)的值。
u8unit;//延時(shí)的單位。
};

3、struct spi_device

struct spi_device用于描述控制器端SPI從設(shè)備。

該數(shù)據(jù)結(jié)構(gòu)為linux內(nèi)核spi子系統(tǒng)的內(nèi)部結(jié)構(gòu)。

struct spi_device定義如下:

structspi_device{
structdevicedev;//設(shè)備的驅(qū)動(dòng)模型表示。
structspi_controller*controller;//與設(shè)備配套使用的SPI控制器。
structspi_controller*master;//控制器的副本(用于為了實(shí)現(xiàn)向后兼容)。
u32max_speed_hz;//此芯片(在此板上)使用的最大時(shí)鐘速率;可能由設(shè)備的驅(qū)動(dòng)程序更改。```spi_transfer.speed_hz```可以在每次傳輸時(shí)覆蓋此設(shè)置。
u8chip_select;//芯片選擇,用于區(qū)分由控制器處理的芯片。
u8bits_per_word;//表示數(shù)據(jù)傳輸涉及的字長(zhǎng),例如8位或12位這樣的字長(zhǎng)很常見(jiàn)??梢栽诿看蝹鬏攕pi_transfer.bits_per_word時(shí)重寫此設(shè)置。
boolrt;//該參數(shù)用于開(kāi)始是否開(kāi)啟實(shí)時(shí)線程優(yōu)先特性。
#defineSPI_NO_TXBIT(31);
#defineSPI_NO_RXBIT(30);
#defineSPI_TPM_HW_FLOWBIT(29);
#defineSPI_MODE_KERNEL_MASK(~(BIT(29)-1));
u32mode;
intirq;//該參數(shù)可能為負(fù)值,或者傳遞給request_irq()以接收來(lái)自該設(shè)備的中斷的數(shù)字。
void*controller_state;//控制器的運(yùn)行狀態(tài)。
void*controller_data;//特定于主板的控制器定義,例如FIFO初始化參數(shù);來(lái)自于board_info.controller_data。
charmodalias[SPI_NAME_SIZE];//要與此設(shè)備一起使用的驅(qū)動(dòng)程序名稱,或該名稱的別名。這出現(xiàn)在用于驅(qū)動(dòng)冷插拔的sysfs的“modalias”屬性中,以及用于熱插拔的事件中。
constchar*driver_override;//如果將驅(qū)動(dòng)程序的名稱寫入此屬性,則設(shè)備將綁定到命名的驅(qū)動(dòng)程序,并且僅綁定到命名的驅(qū)動(dòng)程序。
structgpio_desc*cs_gpiod;//芯片選擇線(CS)的GPIO描述符(該參數(shù)可選,如果不使用GPIO line該參數(shù)為NULL)。
structspi_delayword_delay;//表示在傳送的連續(xù)字(Word)之間插入的延遲。
structspi_delaycs_setup;//表示在CS被斷言后由控制器引入的延遲。
structspi_delaycs_hold;//表示控制器在CS解除斷言之前引入的延遲。
structspi_delaycs_inactive;//CS解除斷言后控制器引入的延時(shí)。如果在spi_transfer中使用cs_change_delay,則兩個(gè)延遲將相加。
structspi_statistics__percpu*pcpu_statistics;//表示spi_device的統(tǒng)計(jì)信息。
};

4、struct spi_driver

struct spi_driver用于描述主機(jī)端“協(xié)議”驅(qū)動(dòng)程序。

為什么叫“協(xié)議”驅(qū)動(dòng)程序,實(shí)屬不易理解,這個(gè)詞我是從官方文檔中的描述(“protocol”)直接音譯過(guò)來(lái),因?yàn)檫@個(gè)結(jié)構(gòu)主要用于基于spi總線協(xié)議通信的從設(shè)備。

struct spi_driver結(jié)構(gòu)實(shí)現(xiàn)如下:

structspi_driver{
conststructspi_device_id*id_table;//描述這個(gè)驅(qū)動(dòng)程序支持的SPI設(shè)備列表。
int(*probe)(structspi_device*spi);//用于將此驅(qū)動(dòng)程序綁定到SPI設(shè)備。驅(qū)動(dòng)程序可以驗(yàn)證設(shè)備是否實(shí)際存在,可能需要配置不需要的特征(例如bits_per_word)。用于系統(tǒng)啟動(dòng)過(guò)程中完成初始配置。
void(*remove)(structspi_device*spi);//從SPI設(shè)備解除與這個(gè)驅(qū)動(dòng)程序的綁定。
void(*shutdown)(structspi_device*spi);//在系統(tǒng)狀態(tài)轉(zhuǎn)換期間使用的標(biāo)準(zhǔn)關(guān)機(jī)回調(diào),如powerdown/halt和kexec。
structdevice_driverdriver;//SPI設(shè)備驅(qū)動(dòng)程序應(yīng)該初始化此結(jié)構(gòu)的name和owner字段。
};

5、struct spi_controller

struct spi_controller描述到SPI主或從控制器的接口。

每個(gè)SPI控制器可以與一個(gè)或多個(gè)spi_device代表的子設(shè)備通信。這些設(shè)備通常使用4線spi總線:共享MOSI, MISO和SCK信號(hào),但不共享芯片選擇信號(hào)。每個(gè)設(shè)備可以配置為使用不同的時(shí)鐘速率。

SPI控制器的驅(qū)動(dòng)程序通過(guò)spi_message事務(wù)隊(duì)列管理對(duì)這些設(shè)備的訪問(wèn),在CPU內(nèi)存和SPI從設(shè)備之間復(fù)制數(shù)據(jù)。對(duì)于它所排隊(duì)的每條這樣的消息,在事務(wù)完成時(shí)將調(diào)用spi_message的*complete回調(diào)函數(shù)。

struct spi_controller實(shí)現(xiàn)如下:

structspi_controller{
structdevicedev;//此驅(qū)動(dòng)程序的設(shè)備接口。
structlist_headlist;//鏈接到全局spi_controller列表。
s16bus_num;//給定SPI控制器的特定板級(jí)標(biāo)識(shí)符。
u16num_chipselect;//chipselects用于區(qū)分各個(gè)SPI從機(jī),編號(hào)從0到num_chipselects。每個(gè)從機(jī)都有一個(gè)芯片選擇信號(hào)。
u16dma_alignment;//SPI控制器對(duì)DMA緩沖區(qū)對(duì)齊的約束。
u32mode_bits;//由控制器驅(qū)動(dòng)程序解析的標(biāo)志。
u32buswidth_override_bits;//要覆蓋此控制器驅(qū)動(dòng)程序的標(biāo)志
u32bits_per_word_mask;//一個(gè)掩碼參數(shù),指示驅(qū)動(dòng)程序支持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;//與此驅(qū)動(dòng)程序相關(guān)的其他約束標(biāo)志
#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;//該結(jié)構(gòu)體的分配是否為線程管理
union{
boolslave;//表示這是一個(gè)SPI從控制器
booltarget;//表示這是一個(gè)SPI目標(biāo)控制器
};
size_t(*max_transfer_size)(structspi_device*spi);//返回spi_device的最大傳輸大小的回調(diào)函數(shù);該回調(diào)函數(shù)指針可能為NULL,這時(shí)候?qū)⑹褂媚J(rèn)的SIZE_MAX。
size_t(*max_message_size)(structspi_device*spi);//返回spi_device的最大消息大小的回函數(shù)調(diào);該回調(diào)函數(shù)可能為NULL,這時(shí)候?qū)⑹褂媚J(rèn)的SIZE_MAX。
structmutexio_mutex;//用于物理總線訪問(wèn)的互斥鎖。
structmutexadd_lock;//該互斥鎖用于避免將設(shè)備添加到相同的芯片選擇。
spinlock_tbus_lock_spinlock;//用于SPI總線鎖定的自旋鎖。
structmutexbus_lock_mutex;//該互斥鎖用于排除多個(gè)調(diào)用者。
boolbus_lock_flag;//表示SPI總線為獨(dú)占使用而被鎖定。
int(*setup)(structspi_device*spi);//更新設(shè)備的SPI控制器使用的設(shè)備模式和時(shí)鐘記錄;協(xié)議代碼可以調(diào)用這個(gè)。如果請(qǐng)求無(wú)法識(shí)別或不支持的模式,則此操作必須失敗。
int(*set_cs_timing)(structspi_device*spi);//SPI設(shè)備請(qǐng)求SPI主控制器配置特定的CS設(shè)置時(shí)間,保持時(shí)間和非活動(dòng)延遲的時(shí)鐘計(jì)數(shù)的回調(diào)函數(shù)。該函數(shù)可選。
int(*transfer)(structspi_device*spi,structspi_message*mesg);//將消息添加到控制器的傳輸隊(duì)列。
void(*cleanup)(structspi_device*spi);//釋放特定于控制器的狀態(tài)
bool(*can_dma)(structspi_controller*ctlr,structspi_device*spi,structspi_transfer*xfer);//判斷該控制器是否支持DMA
structdevice*dma_map_dev;//可以用于DMA映射的設(shè)備。
structdevice*cur_rx_dma_dev;//當(dāng)前用于RX DMA映射的設(shè)備。
structdevice*cur_tx_dma_dev;//當(dāng)前用于TX DMA映射的設(shè)備。
boolqueued;//此控制器是否提供內(nèi)部消息隊(duì)列。
structkthread_worker*kworker;//指向消息pump的線程結(jié)構(gòu)的指針。
structkthread_workpump_messages;//將工作安排到消息pump的工作結(jié)構(gòu)。
spinlock_tqueue_lock;//該自旋鎖用于同步對(duì)消息隊(duì)列的訪問(wèn)。
structlist_headqueue;//消息隊(duì)列。
structspi_message*cur_msg;//當(dāng)前正在傳輸?shù)南ⅰ?structcompletioncur_msg_completion;//完成當(dāng)前正在運(yùn)行的消息。
boolcur_msg_incomplete;//內(nèi)部使用標(biāo)志,用于跳過(guò)cur_msg_completion。此標(biāo)志用于檢查驅(qū)動(dòng)程序是否已經(jīng)調(diào)用了spi_finalize_current_message()。
boolcur_msg_need_completion;//內(nèi)部使用標(biāo)志,用于跳過(guò)cur_msg_completion。此標(biāo)志用于通知正在運(yùn)行spi_finalize_current_message()的上下文,它需要complete()
boolbusy;//用于描述消息pump是否busy。
boolrunning;//用于描述消息pump是否running。
boolrt;//是否將此隊(duì)列設(shè)置為作為實(shí)時(shí)任務(wù)運(yùn)行。
boolauto_runtime_pm;//該標(biāo)志用于描述是否內(nèi)核應(yīng)該確保在硬件準(zhǔn)備好時(shí)保持運(yùn)行時(shí)PM的引用,使用父設(shè)備進(jìn)行擴(kuò)展。
boolcur_msg_mapped;//用于描述消息是否已映射為DMA。
charlast_cs;//表示set_cs記錄的最后一個(gè)chip_select,在非芯片選擇時(shí)值為-1。
boollast_cs_mode_high;
boolfallback;
structcompletionxfer_completion;//該參數(shù)由transfer_one_message()使用
size_tmax_dma_len;//設(shè)備DMA傳輸?shù)淖畲箝L(zhǎng)度。
int(*prepare_transfer_hardware)(structspi_controller*ctlr);//spi子系統(tǒng)請(qǐng)求驅(qū)動(dòng)程序通過(guò)發(fā)出此調(diào)用來(lái)準(zhǔn)備傳輸硬件
int(*transfer_one_message)(structspi_controller*ctlr,structspi_message*mesg);//spi子系統(tǒng)調(diào)用驅(qū)動(dòng)程序來(lái)傳輸單個(gè)消息,同時(shí)對(duì)到達(dá)的傳輸進(jìn)行排隊(duì)。
當(dāng)驅(qū)動(dòng)程序處理完這個(gè)消息后,必須調(diào)用spi_finalize_current_message(),這樣spi子系統(tǒng)才能發(fā)出下一個(gè)消息。
int(*unprepare_transfer_hardware)(structspi_controller*ctlr);//當(dāng)前隊(duì)列上沒(méi)有更多的消息時(shí),spi子系統(tǒng)將通知驅(qū)動(dòng)程序,spi子系統(tǒng)通過(guò)調(diào)用該回調(diào)來(lái)釋放硬件。
int(*prepare_message)(structspi_controller*ctlr,structspi_message*message);//該回調(diào)用于設(shè)置控制器以傳輸單個(gè)消息,例如:進(jìn)行DMA映射,該回調(diào)在線程上下文中調(diào)用。
int(*unprepare_message)(structspi_controller*ctlr,structspi_message*message);//撤銷prepare_message()所做的所有操作。
union{
int(*slave_abort)(structspi_controller*ctlr);//該回調(diào)用于中止SPI從控制器上正在進(jìn)行的傳輸請(qǐng)求。
int(*target_abort)(structspi_controller*ctlr);//該回調(diào)用于中止在SPI目標(biāo)控制器上正在進(jìn)行的傳輸請(qǐng)求
};
void(*set_cs)(structspi_device*spi,boolenable);//設(shè)置芯片選擇線(CS)的邏輯電平??梢詮闹袛嗌舷挛恼{(diào)用。

//傳輸單個(gè)spi_transfer。如果傳輸完成,返回0;如果傳輸仍在進(jìn)行中,返回1。當(dāng)驅(qū)動(dòng)程序完成此傳輸時(shí),它必須調(diào)用spi_finalize_current_transfer(),以便子系統(tǒng)可以發(fā)出下一次傳輸。
int(*transfer_one)(structspi_controller*ctlr,structspi_device*spi,structspi_transfer*transfer);

//spi子系統(tǒng)調(diào)用驅(qū)動(dòng)程序中的該函數(shù)來(lái)處理在transfer_one_message()中發(fā)生的錯(cuò)誤。
void(*handle_err)(structspi_controller*ctlr,structspi_message*message);
conststructspi_controller_mem_ops*mem_ops;//與SPI內(nèi)存交互的優(yōu)化/專用操作。該字段可選,只有在控制器具有內(nèi)存類操作的原生支持時(shí)才應(yīng)該實(shí)現(xiàn)。
conststructspi_controller_mem_caps*mem_caps;//處理內(nèi)存操作的控制器能力。
structgpio_desc**cs_gpiods;//用作芯片選擇線的GPIO描述符數(shù)組;每個(gè)CS號(hào)碼一個(gè)。對(duì)于非gpio(由SPI控制器本身驅(qū)動(dòng))的CS線,任何單個(gè)值都可能為NULL。
booluse_gpio_descriptors;//是否打開(kāi)SPI核心中的代碼來(lái)解析和獲取GPIO描述符,此后將填充cs_gpiods,如果為芯片選擇找到了GPIO線,SPI設(shè)備將分配cs_gpiods。
s8unused_native_cs;//當(dāng)使用cs_gpiods時(shí),spi_register_controller()將用第一個(gè)未使用的原生CS填充此字段,供在使用GPIO CS時(shí)需要驅(qū)動(dòng)原生CS的SPI控制器驅(qū)動(dòng)程序使用。
s8max_native_cs;//當(dāng)使用cs_gpiods并填充此字段時(shí),spi_register_controller()將根據(jù)此值驗(yàn)證所有原生CS(包括未使用的原生CS)。
structspi_statistics__percpu*pcpu_statistics;//為spi_controller的統(tǒng)計(jì)。
structdma_chan*dma_tx;//DMA傳輸通道。
structdma_chan*dma_rx;//DMA接收通道。
void*dummy_rx;//全雙工設(shè)備的虛擬接收緩沖區(qū)
void*dummy_tx;//全雙工設(shè)備的虛擬傳輸緩沖器
int(*fw_translate_cs)(structspi_controller*ctlr,unsignedcs);//如果引導(dǎo)固件使用與Linux期望編號(hào)方案不同,則可以使用這個(gè)可選回調(diào)在二者之間進(jìn)行轉(zhuǎn)換。
boolptp_sts_supported;//如果驅(qū)動(dòng)程序?qū)⑵湓O(shè)置為true,則驅(qū)動(dòng)程序必須在spi_transfer->ptp_sts中提供一個(gè)時(shí)間快照,盡可能接近spi_transfer->ptp_sts_word_pre和spi_transfer->ptp_sts_word_post傳輸?shù)臅r(shí)刻。如果驅(qū)動(dòng)程序沒(méi)有設(shè)置這個(gè)參數(shù),SPI核心將盡可能接近驅(qū)動(dòng)程序移交的快照。
unsignedlongirq_flags;//表示PTP系統(tǒng)時(shí)間戳期間中斷使能狀態(tài)。
boolqueue_empty;//該參數(shù)用于表示消息隊(duì)列是否為空。
boolmust_async;//該參數(shù)用于表示是否關(guān)閉spi框架的所有快速路徑操作。
};

struct spi_controller中的組成元素算比較多的了。

備注:在舊版的linux內(nèi)核中使用struct spi_master描述SPI控制器。在較新的linux內(nèi)核版本中,使用struct spi_controller替換了struct spi_master。

6、struct spi_res

struct spi_res用于描述資源管理結(jié)構(gòu),側(cè)重于在spi_message處理期間的生命周期管理。該結(jié)構(gòu)定義如下:

structspi_res{
structlist_headentry;//資源鏈表項(xiàng)
spi_res_release_trelease;//釋放此資源之前調(diào)用的釋放代碼
unsignedlonglongdata[];//為特定用例分配的額外數(shù)據(jù)
};

7、struct spi_transfer

struct spi_transfer用于描述讀/寫緩沖對(duì),該結(jié)構(gòu)定義定義如下:

structspi_transfer{
constvoid*tx_buf;//要寫入的數(shù)據(jù)(DMA安全內(nèi)存),該值可能為NULL。
void*rx_buf;//要讀取的數(shù)據(jù)(DMA安全內(nèi)存),該值可能為NULL
unsignedlen;//rx和tx緩沖區(qū)的大?。ㄗ止?jié)為單位)
#defineSPI_TRANS_FAIL_NO_STARTBIT(0);
u16error;//SPI控制器驅(qū)動(dòng)程序記錄的錯(cuò)誤狀態(tài)。
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;//用于傳輸?shù)纳⒘斜怼?structsg_tablerx_sg;//用于接收的散列表;
unsigneddummy_data:1;
unsignedcs_off:1;
unsignedcs_change:1;
unsignedtx_nbits:3;//用于寫入的位數(shù)。如果為0,則使用默認(rèn)值(SPI_NBITS_SINGLE)。
unsignedrx_nbits:3;//用于讀取的位數(shù)。如果為0,則使用默認(rèn)值(SPI_NBITS_SINGLE)。
unsignedtimestamped:1;//如果傳輸有時(shí)間戳,則為True
#defineSPI_NBITS_SINGLE0x01;
#defineSPI_NBITS_DUAL0x02;
#defineSPI_NBITS_QUAD0x04;
u8bits_per_word;//為此傳輸選擇一個(gè)bits_per_word,而不是設(shè)備默認(rèn)值。如果為0,則使用默認(rèn)值(來(lái)自spi_device)。
structspi_delaydelay;//在此傳輸之后(可選地)更改chipselect狀態(tài)之前引入的延遲,然后開(kāi)始下一次傳輸或完成此spi_message。
structspi_delaycs_change_delay;//當(dāng)設(shè)置了cs_change并且spi_transfer不是spi_message中的最后一個(gè)時(shí),在cs deassert和assert之間的延遲。
structspi_delayword_delay;//每個(gè)字長(zhǎng)(由bits_per_word設(shè)置)傳輸后引入的字間延遲。
u32speed_hz;//為此傳輸選擇設(shè)備默認(rèn)速度以外的速度。如果為0,則使用默認(rèn)值(來(lái)自spi_device)。
u32effective_speed_hz;//用于傳輸此傳輸?shù)挠行ck速度。如果SPI總線驅(qū)動(dòng)不支持,設(shè)置為0。
unsignedintptp_sts_word_pre;//在tx_buf中的字(受bits_per_word語(yǔ)義約束)偏移量,SPI設(shè)備為此請(qǐng)求開(kāi)始此傳輸?shù)臅r(shí)間快照。在完成SPI傳輸后,該值可能與請(qǐng)求的值相比發(fā)生了變化,這取決于可用的快照分辨率(DMA傳輸、ptp_sts_supported為false等)。
unsignedintptp_sts_word_post;
structptp_system_timestamp*ptp_sts;
structlist_headtransfer_list;//通過(guò)spi_message.transfers進(jìn)行排序的transfer。
};

SPI傳輸總是需要寫入與讀取相同數(shù)量的字節(jié)。struct spi_driverSPI設(shè)備驅(qū)動(dòng)程序應(yīng)該提供rx_buf或tx_buf。在某些情況下,可能還需要為正在傳輸?shù)臄?shù)據(jù)提供DMA地址,當(dāng)?shù)讓域?qū)動(dòng)程序使用DMA時(shí),這樣會(huì)減少CPU開(kāi)銷。

如果傳輸緩沖區(qū)為NULL,則在填充rx_buf時(shí)將移出零。如果接收緩沖區(qū)為NULL,則移入的數(shù)據(jù)將被丟棄。只有l(wèi)en字節(jié)移出(或移進(jìn))。

內(nèi)存中的數(shù)據(jù)總是按照本地CPU字節(jié)順序進(jìn)行排列。例如:當(dāng)bits_per_word為16時(shí),緩沖區(qū)是2N字節(jié)長(zhǎng)(len = 2N),并按CPU字節(jié)順序保存N個(gè)16位字。

當(dāng)SPI傳輸?shù)淖珠L(zhǎng)不是8的2次冪倍數(shù)時(shí),內(nèi)存中的字包含額外的位。內(nèi)存中的字總是被協(xié)議驅(qū)動(dòng)程序視為右對(duì)齊,因此未定義(rx)或未使用(tx)位始終是最重要的位。

所有SPI傳輸從芯片選擇(CS)被激活開(kāi)始。通常,直到消息中的最后一次傳輸之后,它才會(huì)被選中。驅(qū)動(dòng)程序可以使用cs_change影響芯片選擇(CS)信號(hào):

(1)如果傳輸不是消息中的最后一個(gè),則此標(biāo)志用于使CS在消息中間短暫地處于非活動(dòng)狀態(tài)。以這種方式切換CS可能需要終止一個(gè)芯片命令,讓單個(gè)spi_message一起執(zhí)行所有的芯片事務(wù)組。

(2)當(dāng)傳輸是消息中的最后一個(gè)傳輸時(shí),芯片可以保持選中狀態(tài)直到下一次傳輸。在多設(shè)備SPI總線上,沒(méi)有阻止消息到其他設(shè)備,這只是一個(gè)性能提示;向另一個(gè)設(shè)備發(fā)送消息將取消選中該設(shè)備。但在其他情況下,這可以用來(lái)確保正確性,一些SPI設(shè)備需要通過(guò)一系列spi_message提交來(lái)構(gòu)建協(xié)議事務(wù),其中一條消息的內(nèi)容由之前消息的結(jié)果決定,當(dāng)CS處于非活動(dòng)狀態(tài)時(shí),整個(gè)事務(wù)結(jié)束。

當(dāng)SPI可以在1x,2x或4x傳輸時(shí)。它可以通過(guò)tx_nbits和rx_nbits從設(shè)備獲取傳輸信息。在雙向傳輸中,tx_nbits和rx_nbits都應(yīng)該被設(shè)置。用戶可以設(shè)置傳輸模式SPI_NBITS_SINGLE(1x)、SPI_NBITS_DUAL(2x)和SPI_NBITS_QUAD(4x)來(lái)支持這三種傳輸方式。

將spi_message(及其spi_transfers)提交給較低層的代碼負(fù)責(zé)管理其內(nèi)存。因此零初始化沒(méi)有顯式設(shè)置的字段,可以防止未來(lái)API更新。在提交消息及其傳輸之后,忽略它們,直到它完成回調(diào)。

8、struct spi_message

spi_message用于執(zhí)行數(shù)據(jù)傳輸?shù)脑有蛄?,每個(gè)序列由struct spi_transfer表示。該結(jié)構(gòu)定義如下:

structspi_message{
structlist_headtransfers;//該傳輸中傳輸段鏈表
structspi_device*spi;//表示該傳輸?shù)腟PI設(shè)備
unsignedis_dma_mapped:1;//如果為true,則調(diào)用者為每個(gè)傳輸緩沖區(qū)提供DMA和CPU虛擬地址。
boolprepared;//是否為此消息調(diào)用spi_prepare_message()
intstatus;//表示傳輸狀態(tài),0表示成功,否則為負(fù)errno
void(*complete)(void*context);//調(diào)用該回調(diào)以報(bào)告事務(wù)的完成情況。
void*context;//調(diào)用complete()時(shí)的參數(shù)。
unsignedframe_length;//message中的總字節(jié)數(shù)。
unsignedactual_length;//傳輸成功的字節(jié)總數(shù)。
structlist_headqueue;//該參數(shù)供當(dāng)前擁有該消息的驅(qū)動(dòng)程序使用。
void*state;//該參數(shù)供當(dāng)前擁有該消息的驅(qū)動(dòng)程序使用。
structlist_headresources;//用于處理SPI消息時(shí)的資源管理。
structspi_transfert[];//該組成元素用于spi_message_alloc()。(當(dāng)消息和傳輸已經(jīng)一起分配時(shí))
};

一個(gè)spi_message用于執(zhí)行數(shù)據(jù)傳輸?shù)脑有蛄?,每個(gè)序列由struct spi_transfer結(jié)構(gòu)表示。該序列是“原子的”,因?yàn)樵谠撔蛄型瓿芍?,沒(méi)有其他spi_message可以使用該SPI總線。在一些系統(tǒng)中,許多這樣的序列可以作為單個(gè)編程的DMA傳輸來(lái)執(zhí)行。在所有系統(tǒng)上,這些消息都是以隊(duì)列方式組織的,并且可能在發(fā)送到其他設(shè)備的事務(wù)之后完成,發(fā)送到給定spi_device的消息總是按照FIFO順序執(zhí)行。

將spi_message(及其spi_transfers)提交給較底層的代碼負(fù)責(zé)管理其內(nèi)存。使用零初始化沒(méi)有顯式設(shè)置的每個(gè)字段,以隔離后續(xù)可能發(fā)生的API更新帶來(lái)的影響。

9、struct spi_board_info

struct spi_board_info用于SPI設(shè)備的特定板卡模板。該結(jié)構(gòu)定義如下:

structspi_board_info{
charmodalias[SPI_NAME_SIZE];//用于初始化spi_device.modalias,用于識(shí)別驅(qū)動(dòng)程序。
constvoid*platform_data;//用于初始化spi_device.platform_data,用于存儲(chǔ)特定數(shù)據(jù)。
conststructsoftware_node*swnode;//用于描述設(shè)備的軟件節(jié)點(diǎn)
void*controller_data;//用于初始化spi_device.controller_data;一些控制器需要提示硬件設(shè)置,例如DMA。
intirq;//用于初始化spi_device.irq;取決于板卡的連接。
u32max_speed_hz;//用于初始化spi_device.max_speed_hz;基于芯片數(shù)據(jù)表和主板特定信號(hào)質(zhì)量問(wèn)題的限制。
u16bus_num;//識(shí)別哪些spi_controller作為spi_device的父設(shè)備;在spi_new_device()中未使用,取決于板卡接線。
u16chip_select;//用于初始化spi_device.chip_select;取決于板卡連接。
u32mode;//用于初始化spi_device.mode;根據(jù)芯片數(shù)據(jù)表,電路板布線。
};

當(dāng)向設(shè)備樹(shù)中添加新的SPI設(shè)備時(shí),該結(jié)構(gòu)可用作設(shè)備模板,該結(jié)構(gòu)在兩個(gè)地方使用,第一個(gè)作用是可存儲(chǔ)在板卡特定設(shè)備描述符的表中,這些描述符在板卡初始化的早期聲明,然后在控制器的驅(qū)動(dòng)程序初始化之后使用。第二個(gè)作用是作為spi_new_device()調(diào)用的參數(shù)。

四、SPI框架的常用API總結(jié)

linux內(nèi)核不同版本的SPI框架開(kāi)放的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、計(jì)算一個(gè)合適的超時(shí)值
unsignedintspi_controller_xfer_timeout(structspi_controller*ctlr,structspi_transfer*xfer)

//4、同步SPI數(shù)據(jù)傳輸
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注冊(cè)SPI設(shè)備
intspi_register_board_info(structspi_board_infoconst*info,unsignedn)

//11、注冊(cè)一個(gè)SPI驅(qū)動(dòng)
int__spi_register_driver(structmodule*owner,structspi_driver*sdrv)

//12、分配新的SPI設(shè)備
structspi_device*spi_alloc_device(structspi_controller*ctlr)

//13、向SPI核心添加使用spi_alloc_device分配的spi_device
intspi_add_device(structspi_device*spi)

//14、實(shí)例化一個(gè)新的SPI設(shè)備
structspi_device*spi_new_device(structspi_controller*ctlr,structspi_board_info*chip)

//15、注銷單個(gè)SPI設(shè)備
voidspi_unregister_device(structspi_device*spi)

//16、報(bào)告transfer的完成情況
voidspi_finalize_current_transfer(structspi_controller*ctlr)

//17、獲取TX開(kāi)始時(shí)間戳的助手函數(shù)
voidspi_take_timestamp_pre(structspi_controller*ctlr,structspi_transfer*xfer,size_tprogress,boolirqs_off)

//18、獲取TX結(jié)束時(shí)間戳的助手函數(shù)
voidspi_take_timestamp_post(structspi_controller*ctlr,structspi_transfer*xfer,size_tprogress,boolirqs_off)

//19、獲取下一個(gè)排隊(duì)的消息。(由驅(qū)動(dòng)程序調(diào)用用于檢查排隊(duì)的消息)
structspi_message*spi_get_next_queued_message(structspi_controller*ctlr)

//20、由驅(qū)動(dòng)程序調(diào)用,通知內(nèi)核隊(duì)列前面的消息已經(jīng)完成,可以從隊(duì)列中刪除。
voidspi_finalize_current_message(structspi_controller*ctlr)

//21、注冊(cè)輔助SPI設(shè)備(該函數(shù)只能從主SPI設(shè)備的probe函數(shù)中調(diào)用)
structspi_device*spi_new_ancillary_device(structspi_device*spi,u8chip_select)

//22、統(tǒng)計(jì)SpiSerialBus資源的個(gè)數(shù)
intacpi_spi_count_resources(structacpi_device*adev)

//23、中止SPI從控制器上正在進(jìn)行的傳輸請(qǐng)求
intspi_slave_abort(structspi_device*spi)

//24、分配一個(gè)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、注冊(cè)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、當(dāng)單個(gè)傳輸超過(guò)一定大小時(shí),將spi傳輸拆分為多個(gè)傳輸
intspi_split_transfers_maxsize(structspi_controller*ctlr,structspi_message*msg,size_tmaxsize,gfp_tgfp)

//30、當(dāng)單個(gè)傳輸超過(guò)一定數(shù)量的SPI字時(shí),將SPI傳輸拆分為多個(gè)傳輸
intspi_split_transfers_maxwords(structspi_controller*ctlr,structspi_message*msg,size_tmaxwords,gfp_tgfp)

//31、設(shè)置SPI模式和時(shí)鐘速率
intspi_setup(structspi_device*spi)

//32、異步SPI傳輸
intspi_async(structspi_device*spi,structspi_message*message)

//33、阻塞/同步SPI數(shù)據(jù)傳輸
intspi_sync(structspi_device*spi,structspi_message*message)

//34、具有獨(dú)占總線使用的spi_sync()版本
intspi_sync_locked(structspi_device*spi,structspi_message*message)

//35、獲得獨(dú)占SPI總線使用的鎖
intspi_bus_lock(structspi_controller*ctlr)

//36、釋放獨(dú)占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驅(qū)動(dòng)實(shí)例分析

SPI驅(qū)動(dòng)分為兩個(gè)部分:主機(jī)側(cè)驅(qū)動(dòng)設(shè)備側(cè)驅(qū)動(dòng)。

(5-1)SPI主機(jī)側(cè)驅(qū)動(dòng)

(1)SPI主機(jī)側(cè)驅(qū)動(dòng)設(shè)計(jì)思路

一般情況下,SPI主機(jī)側(cè)的驅(qū)動(dòng)程序芯片原廠,會(huì)去實(shí)現(xiàn),并會(huì)合并到自己廠家維護(hù)的linux內(nèi)核版本中發(fā)布給其他基于該芯片設(shè)計(jì)的廠商。在實(shí)現(xiàn)SPi主機(jī)側(cè)驅(qū)動(dòng)的時(shí)候,可以基于平臺(tái)設(shè)備驅(qū)動(dòng)框架實(shí)現(xiàn),然后使用module_platform_driver()或者其他模塊函數(shù)導(dǎo)出,例如:module_init()。在平臺(tái)驅(qū)動(dòng)的.probe指向的函數(shù)中實(shí)現(xiàn)spi驅(qū)動(dòng):

1、區(qū)分spi驅(qū)動(dòng)是slave還是master,并創(chuàng)建對(duì)應(yīng)的struct spi_controller,如果是slave,則使用spi_alloc_slave()創(chuàng)建,如果是master,則使用spi_allov_master()創(chuàng)建。

2、實(shí)現(xiàn)spi寄存器相關(guān)的映射。

3、設(shè)置spi時(shí)鐘。

4、創(chuàng)建spi中斷服務(wù)函數(shù)(以中斷線程化方式實(shí)現(xiàn))。

5、初始化spi_controller相關(guān)組成元素的信息。

6、指定struct spi_controller操作的callback。

7、spi控制器相關(guān)的狀態(tài)獲取和保存。

8、注冊(cè)spi控制器??墒褂胐evm_spi_register_controller()或者相關(guān)接口實(shí)現(xiàn)。

(2)、SPI主機(jī)側(cè)驅(qū)動(dòng)案例分析

本小節(jié),以Rockchip的rk3568的SPI主機(jī)側(cè)驅(qū)動(dòng)為例。分析SPI主機(jī)側(cè)驅(qū)動(dòng)的實(shí)現(xiàn)步驟,rk3568的spi驅(qū)動(dòng)位于/drivers/spi/spi-rockchip.c(以具體linux內(nèi)核源碼為準(zhǔn))文件中。該驅(qū)動(dòng)以platform驅(qū)動(dòng)框架為基礎(chǔ)實(shí)現(xiàn),對(duì)應(yīng)的struct platform_driver實(shí)現(xiàn)如下:

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()導(dǎo)出spi驅(qū)動(dòng)。

接著看看rockchip_spi_dt_match設(shè)備匹配表,定義如下:

5908c992-97ba-11ee-8b88-92fbcf53809c.png

可見(jiàn)該spi驅(qū)動(dòng)支持的芯片類型比較多。

再看看.probe對(duì)應(yīng)的rockchip_spi_probe(),該函數(shù)實(shí)現(xiàn)如下(函數(shù)中內(nèi)容較多):

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(retdev,"Failedtoenableapb_pclk
");
gotoerr_put_ctlr;
}

ret=clk_prepare_enable(rs->spiclk);
if(retdev,"Failedtoenablespi_clk
");
gotoerr_disable_apbclk;
}

spi_enable_chip(rs,false);

ret=platform_get_irq(pdev,0);
if(retdev,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(retdev,"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實(shí)現(xiàn)的主要步驟如下:

讀取spi-slave屬性獲取模式,如果是slave模式,則調(diào)用spi_alloc_slave()分配struct spi_contoller內(nèi)存,否則為master模式,則調(diào)用spi_alloc_master()同樣分配一個(gè)struct spi_contoller內(nèi)存。

獲取基本的IO資源并對(duì)其進(jìn)行映射。

獲取時(shí)鐘并enable時(shí)鐘。

調(diào)用platform_get_irq()獲取中斷號(hào),接著調(diào)用devm_request_threaded_irq()創(chuàng)建中斷處理函數(shù),其中中斷處理函數(shù)為rockchip_spi_isr()。

設(shè)置struct rockchip_spi結(jié)構(gòu)中的組成元素。struct rockchip_spi表示具體的spi控制器。

設(shè)置struct spi_controller 結(jié)構(gòu)中的組成元素。

最后調(diào)用devm_spi_register_controller()注冊(cè)spi控制器。

(5-2)SPI設(shè)備側(cè)驅(qū)動(dòng)

(1)SPI設(shè)備側(cè)驅(qū)動(dòng)設(shè)計(jì)思路

對(duì)于SPI設(shè)備的驅(qū)動(dòng),主要圍繞如何與該SPI設(shè)備進(jìn)行數(shù)據(jù)通信或者實(shí)現(xiàn)控制。在SPI控制器驅(qū)動(dòng)實(shí)現(xiàn)的情況下,SPI設(shè)備側(cè)的實(shí)現(xiàn)思路:

1、對(duì)SPI設(shè)備進(jìn)行描述。

可以通過(guò)修改設(shè)備樹(shù)的方式對(duì)SPI設(shè)備進(jìn)行描述。

2、創(chuàng)建struct spi_driver的具體實(shí)例作為設(shè)備側(cè)驅(qū)動(dòng)。

3、SPI設(shè)備數(shù)據(jù)收發(fā)處理流程

SPI設(shè)備數(shù)據(jù)的收發(fā)主要涉及到兩個(gè)數(shù)據(jù)結(jié)構(gòu):struct spi_message、struct spi_transfer,還需要幾個(gè)用于傳輸?shù)腁PI:

//在使用spi_message之前需要對(duì)其進(jìn)行初始化:
voidspi_message_init(structspi_message*m)

//spi_message初始化完成以后可使用spi_message_add_tail將spi_transfer添加到spi_message隊(duì)列中:
voidspi_message_add_tail(structspi_transfer*t,structspi_message*m)

//spi_message準(zhǔn)備好以后既可以進(jìn)行數(shù)據(jù)傳輸了,數(shù)據(jù)傳輸分為同步傳輸和異步傳輸,同步
//傳輸會(huì)阻塞的等待SPI數(shù)據(jù)傳輸完成,同步傳輸函數(shù)為spi_sync():
intspi_sync(structspi_device*spi,structspi_message*message)

//異步傳輸不會(huì)阻塞的等到SPI數(shù)據(jù)傳輸完成,異步傳輸需要設(shè)置spi_message中的complete成員變量,complete是一個(gè)回調(diào)函數(shù),當(dāng)SPI異步傳輸完成以后此函數(shù)就會(huì)被調(diào)用。SPI異步傳
//輸函數(shù)為spi_async():
intspi_async(structspi_device*spi,structspi_message*message)

4、根據(jù)具體驅(qū)動(dòng)需求設(shè)計(jì)struct file_operations對(duì)應(yīng)的ops的callback。

5、在.probe中使用spi_register_driver()向SPI核心注冊(cè)SPI驅(qū)動(dòng),以字符設(shè)備類方式導(dǎo)出用戶空間SPI設(shè)備文件,分配中斷等

6、以驅(qū)動(dòng)框架方式導(dǎo)出。

(2)、SPI設(shè)備側(cè)驅(qū)動(dòng)案例分析

當(dāng)主機(jī)側(cè)的SPI實(shí)現(xiàn)后,我們可以快速使用SPI控制器與SPI設(shè)備進(jìn)行通信了。本小節(jié)以icm20608這款常見(jiàn)的SPI接口的六軸傳感器為例分析SPI設(shè)備側(cè)驅(qū)動(dòng)的具體實(shí)現(xiàn)步驟。

首先使用struct spi_driver創(chuàng)建spi驅(qū)動(dòng)實(shí)例icm20608_driver,指定其中的.probe、.remove、driver參數(shù),如有必要可實(shí)現(xiàn).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()初始化模塊,在模塊初始化函數(shù)中調(diào)用spi_register_driver()將icm20608_driver注冊(cè)到linux內(nèi)核。然后實(shí)現(xiàn)模塊退出接口函數(shù),在該函數(shù)中實(shí)現(xiàn)必要的退出清理操作。

接著實(shí)現(xiàn)icm20608_probe()、icm20608_remove和設(shè)備樹(shù)匹配表icm20608_of_match。

在icm20608_probe()中以字符設(shè)備方式向用戶暴露出設(shè)備文件。并實(shí)現(xiàn)對(duì)icm20608設(shè)備文件的struct file_operations中的callback。例如:.open、.read、release。因icm20608為六軸傳感器,主要操作是讀取數(shù)據(jù),所以.write可以不用實(shí)現(xiàn)。

在實(shí)現(xiàn).read操作時(shí),主要內(nèi)容是使用SPI控制器發(fā)送相應(yīng)控制數(shù)據(jù)到SPI設(shè)備。使用SPI控制器發(fā)送數(shù)據(jù)的代碼如下:

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、申請(qǐng)內(nèi)存*/
transfer=kzalloc(sizeof(structspi_transfer),GFP_KERNEL);

/*2、發(fā)送要讀取的寄存地址*/
txdata[0]=reg|0x80;/*寫數(shù)據(jù)的時(shí)候寄存器地址bit8要置1*/
transfer->tx_buf=txdata;/*要發(fā)送的數(shù)據(jù)*/
transfer->len=1;/*1個(gè)字節(jié)*/

/*3、初始化spi_message*/
spi_message_init(&message);

/*4、將spi_transfer添加到spi_message隊(duì)列*/
spi_message_add_tail(transfer,&message);

/*5、同步發(fā)送*/
ret=spi_sync(spi,&message);

/*6、釋放內(nèi)存*/
kfree(transfer);

returnret;
}

六、SPI驅(qū)動(dòng)調(diào)試總結(jié)

在SPI驅(qū)動(dòng)調(diào)試過(guò)程中,還是需要注意以下幾點(diǎn):

(1)確保自己系統(tǒng)的SPI成功運(yùn)行。這是SPI設(shè)備能正常通信工作的前提條件。

(2)在進(jìn)行SPI數(shù)據(jù)傳輸時(shí),確認(rèn)時(shí)序是否正確:發(fā)送引腳有正常的波形,CLK引腳有正常的時(shí)鐘信號(hào),CS引腳有拉低。

(3)確保SPI的4個(gè)引腳的引腳復(fù)用配置正確。







審核編輯:劉清

聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場(chǎng)。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問(wèn)題,請(qǐng)聯(lián)系本站處理。 舉報(bào)投訴
  • 嵌入式
    +關(guān)注

    關(guān)注

    5052

    文章

    18909

    瀏覽量

    300710
  • 觸摸傳感器
    +關(guān)注

    關(guān)注

    0

    文章

    121

    瀏覽量

    23034
  • EEPROM
    +關(guān)注

    關(guān)注

    9

    文章

    1006

    瀏覽量

    81218
  • SPI通信
    +關(guān)注

    關(guān)注

    0

    文章

    35

    瀏覽量

    11325
  • LINUX內(nèi)核
    +關(guān)注

    關(guān)注

    1

    文章

    315

    瀏覽量

    21579

原文標(biāo)題:linux內(nèi)核中的spi“簡(jiǎn)單”嗎?

文章出處:【微信號(hào):嵌入式小生,微信公眾號(hào):嵌入式小生】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。

收藏 人收藏

    評(píng)論

    相關(guān)推薦

    SPI接口初始化函數(shù)spi_master_init

    有誰(shuí)知道SPI接口初始化函數(shù)spi_master_init 和 SPI接口數(shù)據(jù)傳送函數(shù)SPI_Transmit ?哪里能找到資料?搜索百度沒(méi)
    發(fā)表于 04-03 09:16

    SPI模塊的初始化代碼

    第一次寫博客有點(diǎn)緊張哈哈哈所以話不多說(shuō)先上代碼壓壓驚//以下是SPI模塊的初始化代碼,配置成主機(jī)模式//SPI初始化//這里針是對(duì)SPI2
    發(fā)表于 08-04 07:17

    STM32的SPI1、SPI2、SPI3初始化及RF1101的應(yīng)用

    STM32的SPI1、SPI2、SPI3初始化及RF1101的應(yīng)用(標(biāo)準(zhǔn)庫(kù)與HAL庫(kù))——基于STM32F103RCT6---- 標(biāo)準(zhǔn)庫(kù):說(shuō)明:相關(guān)文件共有兩個(gè):bsp_
    發(fā)表于 08-05 07:53

    SPI主機(jī)驅(qū)動(dòng)Linux簡(jiǎn)

    1. LinuxSPI驅(qū)動(dòng)框架1.1 SPI主機(jī)驅(qū)動(dòng)Linux使用spi_master結(jié)構(gòu)體表
    發(fā)表于 11-02 07:50

    LINUX系統(tǒng)引導(dǎo)和初始化-LINUX內(nèi)核解讀

    Linux 的系統(tǒng)引導(dǎo)和初始化 ----------Linux2.4.22內(nèi)核解讀之一 一、 系統(tǒng)引導(dǎo)和初始化概述 相關(guān)代碼(引導(dǎo)扇區(qū)的程序
    發(fā)表于 11-03 22:31 ?53次下載

    Linux內(nèi)存初始化

    之前有幾篇博客詳細(xì)介紹了Xen的內(nèi)存初始化,確實(shí)感覺(jué)這部分內(nèi)容蠻復(fù)雜的。這兩天在看Linux內(nèi)核啟動(dòng)內(nèi)存的初始化,也是看的云里霧里的,想嘗
    發(fā)表于 10-12 11:16 ?0次下載

    解析內(nèi)核初始化時(shí)根內(nèi)存盤的加載過(guò)程

    2006-12-12 13:54:41 來(lái)源:Linux 寶庫(kù) 分享到:標(biāo)簽:loadlin gzip 作者:opera 概述 ==== 1)當(dāng)內(nèi)核配置了內(nèi)存盤時(shí), 內(nèi)核初始化時(shí)可以
    發(fā)表于 11-08 10:40 ?0次下載

    uboot和內(nèi)核里phy的初始化_內(nèi)核里的雙網(wǎng)絡(luò)配置及phy的初始化

    uboot 和內(nèi)核里 phy 的初始化,以及內(nèi)核里的雙網(wǎng)絡(luò)配置及 phy 的初始化。 本文以盈鵬飛嵌入式的CoM-335x(基于AM335x)核心
    的頭像 發(fā)表于 05-17 08:19 ?1.2w次閱讀

    SD卡的SPI模式的初始化順序

    SD卡的SPI模式的初始化順序 這些天沒(méi)有出門,一直在家研究SD卡的SPI模式的初始化順序,這里為大家總結(jié)了一下編寫該程序所需要的知識(shí): SD卡的官方資料(我承認(rèn)這個(gè)資料很垃圾,比起民
    發(fā)表于 09-21 14:34 ?1056次閱讀

    單片機(jī)SPI初始化設(shè)計(jì)

    6 //這里只針對(duì)SPI初始化 7 GPIOA-》CRL&=0X000FFFFF; 8 GPIOA-》CRL|=0XBBB00000;//PA5.6.7復(fù)用推挽輸出 9
    發(fā)表于 12-06 15:55 ?2315次閱讀

    Linux內(nèi)核初始化過(guò)程的調(diào)用順序

    所有的__init函數(shù)在區(qū)段.initcall.init還保存了一份函數(shù)指針,在初始化時(shí)內(nèi)核會(huì)通過(guò)這些函數(shù)指針調(diào)用這些__init函數(shù)指針,并在整個(gè)初始化完成后,釋放整個(gè)init區(qū)段
    發(fā)表于 05-12 08:40 ?1587次閱讀

    嵌入式Linux SPI驅(qū)動(dòng)

    1. LinuxSPI驅(qū)動(dòng)框架1.1 SPI主機(jī)驅(qū)動(dòng)Linux使用spi_master結(jié)構(gòu)體表
    發(fā)表于 11-01 17:05 ?14次下載
    嵌入式<b class='flag-5'>Linux</b> <b class='flag-5'>SPI</b>驅(qū)動(dòng)

    stm32單片機(jī)用spi初始化sd卡的一些問(wèn)題及反思

    初次接觸使用spi接口讀寫sd卡,在初始化階段一直過(guò)不去,但隨著這幾天的學(xué)習(xí)也有一些心得,分享出來(lái),供大家參考使用,新手摸石頭過(guò)河,多有不足,還請(qǐng)斧正??!
    發(fā)表于 12-22 19:19 ?3次下載
    stm32單片機(jī)用<b class='flag-5'>spi</b><b class='flag-5'>初始化</b>sd卡<b class='flag-5'>中</b>的一些問(wèn)題及反思

    STM32F103學(xué)習(xí)筆記——SPI讀寫Flash(二)

    ??此系列文章是小白學(xué)習(xí)STM32的一些學(xué)習(xí)筆記。小白第一次寫筆記文章,有不足或是錯(cuò)誤之處,請(qǐng)多體諒和交流!目錄1.軟件設(shè)計(jì)流程2.SPI初始化3.SPI發(fā)送接收一字節(jié)函數(shù)編寫4.FLASH控制指令
    發(fā)表于 12-22 19:30 ?10次下載
    STM32F103學(xué)習(xí)筆記——<b class='flag-5'>SPI</b>讀寫Flash(二)

    SPI復(fù)用的初始化覆蓋問(wèn)題-數(shù)據(jù)不對(duì),而且寫的數(shù)據(jù)不一樣,位數(shù)還在變化。

    器件寫的數(shù)據(jù)不但不對(duì),而且寫使能函數(shù)寫入不用的的數(shù)據(jù)時(shí),數(shù)據(jù)的位數(shù)還在變化。最后看到前面一個(gè)器件初始化配置后,后面緊跟著針對(duì)另一個(gè)器件進(jìn)行SPI初始化,想到可能是
    發(fā)表于 12-22 19:35 ?3次下載
    <b class='flag-5'>SPI</b>復(fù)用的<b class='flag-5'>初始化</b>覆蓋問(wèn)題-數(shù)據(jù)不對(duì),而且寫的數(shù)據(jù)不一樣,位數(shù)還在變化。