SPI總線概覽

SPI是什麼?


Serial Peripheral Interface是一種同步4線串口鏈路,用於連接傳感器、內存和外設到微控制器。他是一種簡單的事實標準,還不足以複雜到需要一份正式的規範。SPI使用主/從配置模式。


3根控制數據傳輸,其中包含並行數據線:MOSIMasterout Slave in)MISOMasterin Slave out). 有四種時鐘模式用於數據交換:mode-0mode-3是經常使用的模式。當沒有數據要傳輸時,SCK線會處於空閒狀態(低或高)。


大多時候系統中一根SPI總線上捆着很多的從設備,SPI控制器靠片選信號線來激活某個從設備。所有的SPI從設備都必須支持片選信號,有的還會有額外的中斷信號線連接至主控制器。


不像USBSMBus這類串行總線,廠家間的SPI設備可能並不能協同工作。

- SPI可能被用在請求/相應式設備協議,比如觸摸屏還有內存芯片;

- 可半雙工/全雙工傳輸數據;

-不同設備可能使用的字長不一樣,有些是8bit字長,像數字採樣設備就可能是12或者20-bit字長流;

- 大多時候總是先發送字的MSB位,也可能是LSB位;

- 有時候SPI可用在鏈式設備中:移位寄存器


類似的,SPI也極少支持自舉的協議類型。SPI從設備樹只能靠配置表來手動配置。


有些個SPI設備只用3根線:SCKdatanCSx,其中data也叫做MOMISISO,就是MOSIMISO合併以實現半雙工,每次要麼讀要麼寫。


微控制器大部分支持主控制器和從設備的。這裏內核只支持主控制器部分了。


誰會用到他?又是在在那些系統中呢?

linux開發者使用SPI來爲嵌入式系統編寫設備驅動。SPI可以用來控制外部芯片,而且衆多的MMCSD卡都支持SPI協議。一些老式的PC機還使用SPIflash或者BIOS代碼。


SPI從芯片包含很廣的範圍像數模轉換,內存還有並行的USB控制器,甚至以太網適配器,數不勝數。


大多數系統使用的SPI都是一集成了多個設備的主板。一些提供像擴展接頭的SPI鏈路,可以使用GPIO來創建低速的“bitbanging”適配器。很少會熱插撥一個SPI控制器的,使用他就是處於低成本和簡潔操作,如果側重動態重新配置,那麼USB會是個更好的選擇。


許多可以運行linux的微控制器都已經集成了一個或多個SPI模式的I/O接口。打開SPI支持功能就無需特別的MMC/SD/SDIO控制器就可以使用MMCSD卡了。


SPI的幾個clockmode到底是什麼了?CPOLCPHA又是什麼了?他們如何區分呢?

這裏有篇博文介紹的很好:http://blog.csdn.net/ce123/article/details/6923293


這些個驅動編程接口是如何工作的呢?

<linux/spi/spi.h>頭文件中包含有內核文檔,做爲主要的源碼,你應該詳讀內核API文檔的相關章節。本文只是概覽,在瞭解細節前有個大致的圖景是好的。

SPI請求會進入到I/O隊列中。請求給定的SPI設備也是按照FIFO順序進行的,通過完成機制異步通知。也同簡單的同步措施:先寫在讀出來。

有倆類SPI驅動:

控制器驅動(Controller drivers...集成在SOC中的控制器,經常扮演MasterSlave雙角色。這類驅動直接接觸到硬件層的寄存器甚至使用DMA。亦或者扮演bitbanger,僅需要GPIO腳。


協議驅動(Protocoldrivers...在控制器和slave或者控制器和另外一條SPI鏈路上的Master傳遞消息。協議驅動是將控制器讀到的數據,比如是一堆0、1代碼,解析成有意義的協議數據。


對於協議驅動應該是我們要寫的,spilinux內核中有spi子系統分爲spi核心層,就類似USBcore一樣是主控制器部分,另一個就是spi設備層了。前者內核幫咱寫好了,爲了讓你的spi設備能工作,就得藉助spicontroller driver導出的一些設施來編寫protocoldrivers了。


struct spi_device結構封裝了倆類驅動間的master-side接口。


有一個最小化SPI編程接口的core,專注於使用板級初始化代碼提供的設備表並藉助於驅動模型來連接controllerprotocol驅動。在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時鐘頻率是120Kirq線是怎麼連接的等等。


board_info中應該提供足夠的信息使芯片驅動未加載前系統可以正常工作。最爲麻煩的方面就是spi_device.modeSPI_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_infomodalias域爲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()。異步請求可能在上下文(中斷處理,tasketc)中提交,通過消息回調來報告完成(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_messagespi_transfer元數據是用來將I/O緩衝粘合成一個protocol事務。


如果你喜歡,spi_message_alloc()spi_message_free()可以方便的申請spi_message並初始化他們。


我該如何編寫SPIMasster Controller驅動呢?

詳見原文:http://www.kernel.org/doc/Documentation/spi/spi-summary

發佈了29 篇原創文章 · 獲贊 55 · 訪問量 19萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章