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

你可能感兴趣的:(linux,工作,struct,文档,interface,linux内核)