SPI是什麼?
Serial Peripheral Interface是一種同步4線串口鏈路,用於連接傳感器、內存和外設到微控制器。他是一種簡單的事實標準,還不足以複雜到需要一份正式的規範。SPI使用主/從配置模式。
有3根控制數據傳輸,其中包含並行數據線:MOSI(Masterout Slave in)和MISO(Masterin Slave out). 有四種時鐘模式用於數據交換:mode-0和mode-3是經常使用的模式。當沒有數據要傳輸時,SCK線會處於空閒狀態(低或高)。
大多時候系統中一根SPI總線上捆着很多的從設備,SPI控制器靠片選信號線來激活某個從設備。所有的SPI從設備都必須支持片選信號,有的還會有額外的中斷信號線連接至主控制器。
不像USB、SMBus這類串行總線,廠家間的SPI設備可能並不能協同工作。
- SPI可能被用在請求/相應式設備協議,比如觸摸屏還有內存芯片;
- 可半雙工/全雙工傳輸數據;
-不同設備可能使用的字長不一樣,有些是8bit字長,像數字採樣設備就可能是12或者20-bit字長流;
- 大多時候總是先發送字的MSB位,也可能是LSB位;
- 有時候SPI可用在鏈式設備中:移位寄存器
類似的,SPI也極少支持自舉的協議類型。SPI從設備樹只能靠配置表來手動配置。
有些個SPI設備只用3根線:SCK,data,nCSx,其中data也叫做MOMI或SISO,就是MOSI和MISO合併以實現半雙工,每次要麼讀要麼寫。
微控制器大部分支持主控制器和從設備的。這裏內核只支持主控制器部分了。
誰會用到他?又是在在那些系統中呢?
linux開發者使用SPI來爲嵌入式系統編寫設備驅動。SPI可以用來控制外部芯片,而且衆多的MMC、SD卡都支持SPI協議。一些老式的PC機還使用SPIflash或者BIOS代碼。
SPI從芯片包含很廣的範圍像數模轉換,內存還有並行的USB控制器,甚至以太網適配器,數不勝數。
大多數系統使用的SPI都是一集成了多個設備的主板。一些提供像擴展接頭的SPI鏈路,可以使用GPIO來創建低速的“bitbanging”適配器。很少會熱插撥一個SPI控制器的,使用他就是處於低成本和簡潔操作,如果側重動態重新配置,那麼USB會是個更好的選擇。
許多可以運行linux的微控制器都已經集成了一個或多個SPI模式的I/O接口。打開SPI支持功能就無需特別的MMC/SD/SDIO控制器就可以使用MMC和SD卡了。
SPI的幾個clockmode到底是什麼了?CPOL和CPHA又是什麼了?他們如何區分呢?
這裏有篇博文介紹的很好:http://blog.csdn.net/ce123/article/details/6923293
這些個驅動編程接口是如何工作的呢?
在<linux/spi/spi.h>頭文件中包含有內核文檔,做爲主要的源碼,你應該詳讀內核API文檔的相關章節。本文只是概覽,在瞭解細節前有個大致的圖景是好的。
SPI請求會進入到I/O隊列中。請求給定的SPI設備也是按照FIFO順序進行的,通過完成機制異步通知。也同簡單的同步措施:先寫在讀出來。
有倆類SPI驅動:
控制器驅動(Controller drivers)...集成在SOC中的控制器,經常扮演Master和Slave雙角色。這類驅動直接接觸到硬件層的寄存器甚至使用DMA。亦或者扮演bitbanger,僅需要GPIO腳。
協議驅動(Protocoldrivers)...在控制器和slave或者控制器和另外一條SPI鏈路上的Master傳遞消息。協議驅動是將控制器讀到的數據,比如是一堆0、1代碼,解析成有意義的協議數據。
對於協議驅動應該是我們要寫的,spi在linux內核中有spi子系統分爲spi核心層,就類似USBcore一樣是主控制器部分,另一個就是spi設備層了。前者內核幫咱寫好了,爲了讓你的spi設備能工作,就得藉助spicontroller driver導出的一些設施來編寫protocoldrivers了。
struct spi_device結構封裝了倆類驅動間的master-side接口。
有一個最小化SPI編程接口的core,專注於使用板級初始化代碼提供的設備表並藉助於驅動模型來連接controller和protocol驅動。在sysfs文件系統中,SPI視圖:
/sys/devices/.../CTLR ... physicalnode for a given SPI controller
/sys/devices/.../CTLR/spiB.C ...spi_device on bus "B",
chipselect C, accessed through CTLR.
/sys/bus/spi/devices/spiB.C ...symlink to that physical
.../CTLR/spiB.C device
/sys/devices/.../CTLR/spiB.C/modalias ... identifies the driver
that should be used with this device(for hotplug/coldplug)
/sys/bus/spi/drivers/D ... driverfor one or more spi*.* devices
/sys/class/spi_master/spiB ...symlink (or actual device node) to
a logical node which could hold classrelated state for the
controller managing bus "B". All spiB.* devices share one
physical SPI bus segment, with SCLK,MOSI, and MISO.
板級初始代碼中是如何申明SPI設備的呢?
linux需要多種信息來配置SPI設備。這些信息就是有板級初始代碼提供的。
聲明controllers
第一類信息:是一個類表,表明存在那種的SPI控制器。對於SOC來說,就是平臺設備platformdevices,這就需要一些platform_data來進行正確的操作。structplatform_device就包含設備的第一個寄存器的起始物理地址和IRQ號。
platform也將抽象註冊spi控制器這一操作,也可以將這段通用代碼共享到使用同一種控制器的多個板子中去。
舉個例子在arch/../mach-*/board-*.c中,
#include <mach/spi.h> /* formysoc_spi_data */
/* if your mach-* infrastructuredoesn't support kernels that can
* run on multiple boards, pdatawouldn't benefit from "__init".
*/
static struct mysoc_spi_data__initdata pdata = { ... };
static __init board_init(void)
{
...
/* this board only uses SPIcontroller #2 */
mysoc_register_spi(2, &pdata);
...
}
And SOC-specific utility code mightlook something like:
#include <mach/spi.h>
static struct platform_device spi2 = {... };
void mysoc_register_spi(unsigned n,struct mysoc_spi_data *pdata)
{
struct mysoc_spi_data *pdata2;
pdata2 = kmalloc(sizeof *pdata2,GFP_KERNEL);
*pdata2 = pdata;
...
if (n == 2) {
spi2->dev.platform_data = pdata2;
register_platform_device(&spi2);
/* also: set up pin modes so thespi2 signals are
* visible on the relevant pins ...bootloaders on
* production boards may alreadyhave done this, but
* developer boards will often needLinux to do it.
*/
}
...
}
platform_data包含哪些內容具體看如何使用他們了,內置時鐘還是外部時鐘都有所區別。
聲明Slave設備
第二類信息也是個列表:目標板上有哪種SPIslave設備,提供板級數據使設備能夠正常工作。
arch/.../mach-*/board-*.c文件中有個小列表:板載的SPI設備有那些。
static struct ads7846_platform_dataads_info = {
.vref_delay_usecs = 100,
.x_plate_ohms = 580,
.y_plate_ohms = 410,
};
static struct spi_board_infospi_board_info[] __initdata = {
{
.modalias = "ads7846",
.platform_data = &ads_info,
.mode = SPI_MODE_0,
.irq = GPIO_IRQ(31),
.max_speed_hz = 120000 /* max samplerate at 3V */ * 16,
.bus_num = 1,
.chip_select = 0,
},
};
同樣的,不論板載信息是什麼,每個芯片可能需要多種類型。上面告訴我們這個設備的最大SPI時鐘頻率是120K,irq線是怎麼連接的等等。
board_info中應該提供足夠的信息使芯片驅動未加載前系統可以正常工作。最爲麻煩的方面就是spi_device.mode中SPI_CS_HIGH位域了,在知道如何取消選擇之前使和一個設備共享總線是不可能的。
你的板級初始代碼會使用SPI設施來註冊哪個table,當SPImastercontroller驅動註冊完成後就可以使用了:
spi_register_board_info(spi_board_info,ARRAY_SIZE(spi_board_info));
正如其他的板級設置,你無需註銷他們。
非靜態配置
開發板相較於最終的產品扮演者不同的角色,比如潛在的熱插撥SPI設備或控制器的需求。
在這種情形下你得使用spi_busnum_to_master()來查看busmaster,也可能使用spi_new_device()爲熱插撥板卡提供board_info信息。最後,你得手動的註銷你註冊過得資源:spi_unregister_device().
當linux通過SPI提供對MMC/SD/SDIO/DataFlash的支持時,所需的配置就是動態的了。幸運的是,這類設備都支持基礎的設備標識符探測,因而他們是可以熱插撥的。
如何編寫SPIProtocol Driver呢?
現在的大部分的SPI驅動都是內核空間的,也有支持用戶空間的驅動。這裏只涉及內核空間的驅動。
SPI Protocol 驅動有點像整合的platform驅動:
static struct spi_driver CHIP_driver ={
.driver = {
.name = "CHIP",
.owner = THIS_MODULE,
},
.probe = CHIP_probe,
.remove = __devexit_p(CHIP_remove),
.suspend = CHIP_suspend,
.resume = CHIP_resume,
};
驅動核心會自動嘗試將這個驅動綁定到board_info中modalias域爲CHIP的設備上。你的probe應該是這個樣子的,除非你打算編寫管理總線的代碼:
static int __devinit CHIP_probe(structspi_device *spi)
{
struct CHIP *chip;
struct CHIP_platform_data *pdata;
/* assuming the driver requiresboard-specific data: */
pdata = &spi->dev.platform_data;
if (!pdata)
return -ENODEV;
/* get memory for driver's per-chipstate */
chip = kzalloc(sizeof *chip,GFP_KERNEL);
if (!chip)
return -ENOMEM;
spi_set_drvdata(spi, chip);
... etc
return 0;
}
當進入probe時,驅動就可能激發I/O請求並使用spi_message發送到SPI設備。
- spi_message是一系列的協議操作,並以原子方式進行:
+ 什麼時候開始讀寫...由spi_transfer的請求次序來決定;
+ 使用那個I/O緩衝...每個spi_transfer在每個傳輸方向上都依附一個buffer,支持雙工(有倆個pointer)和半雙工;
+ 每次傳輸後的可選定義延遲...spi_transfer.delay_usecs設置;
+ 傳輸後片選信號是高是低...spi_transfer.cs_change標誌決定;
+ 下一個message是否傳輸到同一個設備...看spi_transfer.cs_change在最後一次transfer中的狀態,可以減少片選信號變更操作時的開銷。
-遵循標準內核規則,在你的message中提供安全的DMA緩衝。除非硬件請求,controller驅動並不強制使用DMA機制來減少額外的複製。
如果使用標準的dma_map_single()(系統提供的)來處理那些緩衝不合適,可以使用spi_message.is_dma_mapped來通知contrller驅動,你已經提供了相應的DMA地址(驅動自身提供的?)。
-基本的I/O原語是spi_async()。異步請求可能在上下文(中斷處理,task,etc)中提交,通過消息回調來報告完成(completion)。任何錯誤會取消片選,一切spi_message會被中止。
- 同樣也有spi_sync(),spi_read(),spi_write and spi_write_then_read().這些只會在可能引起睡眠的上下文中提交,他們比spi_async()要安全可靠。
-spi_write_then_read()應該只包含短小的片段用於數據傳輸,在開銷可以忽略的地方調用他。這被設計用來支持RPC風格的請求:寫一個8bit的命令,接着讀一個16bit的響應--spi_w8r16()就是其中一類封裝了。
有些驅動需要修改spi_device屬性,比如transfer模式,字大小或者是時鐘頻率。可以在第一次I/O完成前在probe()中調用spi_setup()來修改。當然,在設備沒有messages時,可以在任何時候進行調用。
spi_device也許是驅動的最低層了,其上層可能包括sysfs(尤其是傳感器數據讀取),輸入層,ALSA,網絡,MTD,字符設備驅動框架,或者是其他linux子系統。
在和SPI設備交互時,有兩類內存驅動是需要管理的。
-I/O緩衝使用通常的linux規則,且必須是DMA安全的。可以從堆中或者空閒內存頁池中申請。絕不可以使用棧和加註任何static聲明。
-spi_message和spi_transfer元數據是用來將I/O緩衝粘合成一個protocol事務。
如果你喜歡,spi_message_alloc()和spi_message_free()可以方便的申請spi_message並初始化他們。
我該如何編寫SPIMasster Controller驅動呢?
詳見原文:http://www.kernel.org/doc/Documentation/spi/spi-summary