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