1. 什么是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设备可能并不能协同工作.
- SPI可能被用在请求/相应式设备协议,比如触摸屏还有内存芯片;
- 可半双工/全双工传输数据;
- 不同设备可能使用的字长不一样,有些是8bit字长,像数字采样设备就可能是12或者20-bit字长流;
- 大多时候总是先发送字的MSB位,也可能是LSB位;
- 有时候SPI可用在链式设备中:移位寄存器
类似的,SPI也极少支持自举的协议类型.SPI从设备树只能靠配置表来手动配置.
有些个SPI设备只用3根线:SCK,data,nCSx,其中data也叫做MOMI或SISO,就是MOSI和MISO合并以实现半双工,每次要么读要么写.
微控制器大部分支持主控制器和从设备的.这里内核只支持主控制器部分了.
2. 谁会用到它?又是在哪些系统中呢?
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卡了.
3. SPI的几个clockmode到底是什么了?CPOL和CPHA又是什么了?他们如何区分呢?
这很容易混淆,而且在供应商的文档里你会发现找不到有帮助的东西,这四种模式结合了两个模式位来标志.
- CPOL表示初始时钟极性,CPOL=0表示时钟开始值是低电平,所以第一阶段(前沿)的时候会处在上升沿,第二阶段(后沿)是下降沿.CPOL=1表示时钟开始是高电平,所以第一阶段(前沿)就是下降沿;
- CPHA表示时钟相位用于采样数据.CPHA=0说明在(前沿)期间进行采样.CPHA=1说明在后沿进行采样;
由于在进行采样之前,信号必须是处于稳定状态,CPHA=0意味着它的数据会在第一个时钟周期的边沿(前沿)之前的半个时钟被写入.片选使之成为了可能.
芯片手册并不总是会说"uses SPI mode X"在尽可能多的单词中.但是她们的时序图将使CPOL和CPHA模式更加清晰.
在SPI模式编码中,CPOL是在高位而CPHA在地位.所以当一个时序图现实时钟起点是低电平(CPOL=0),并且数据稳定在采样期间为时钟后沿,则就是SPI模式1.
请注意时钟模式是和片选相关的,它刚一设置好片选就被激活了.所以主机master在选择一个从设备之前必须将时钟设置为无效,然后从设备在它的片选线被激活的时候通过采样时钟水平就会告诉时钟极性选择.这就是为什么许多模式支持例如模式0和模式3:他们不在乎时钟极性,也不在乎在时钟上升沿的时钟数据的输入/输出.
关于时钟极性和相位,可以参看这篇博文:http://www.cnblogs.com/jason-lu/articles/3713319.html
4. 这些驱动程序接口是如何工作的?
在<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视图:
1 /sys/devices/.../CTLR ... physical node for a given SPI controller 2 3 /sys/devices/.../CTLR/spiB.C ... spi_device on bus "B", 4 chipselect C, accessed through CTLR. 5 6 /sys/bus/spi/devices/spiB.C ... symlink to that physical 7 .../CTLR/spiB.C device 8 9 /sys/devices/.../CTLR/spiB.C/modalias ... identifies the driver 10 that should be used with this device (for hotplug/coldplug) 11 12 /sys/bus/spi/drivers/D ... driver for one or more spi*.* devices 13 14 /sys/class/spi_master/spiB ... symlink (or actual device node) to 15 a logical node which could hold class related state for the 16 controller managing bus "B". All spiB.* devices share one 17 physical SPI bus segment, with SCLK, MOSI, and MISO.
需要注意的是控制器类状态的实际位置取决于您是否开启CONFIG_SYSFS_DEPRECATED标志.此时,唯一的特定类状态是总线编号("B" in "spiB"),所以/sys/class下的那些入口项是唯一的识别总线的标志.
5. 板级初始化代码中是如何声明SPI设备的?
linux需要多种信息来配置SPI设备.这些信息就是由板级初始代码提供的.
5.1 声明controllers
第一类信息:是一个类表,表明存在那种的SPI控制器.对于SOC来说,就是平台设备platformdevices,这就需要一些platform_data来进行正确的操作.structplatform_device就包含设备的第一个寄存器的起始物理地址和IRQ号.
platform也将抽象注册spi控制器这一操作,也可以将这段通用代码共享到使用同一种控制器的多个板子中去.
举个例子在arch/../mach-*/board-*.c中,可能会看到如下的代码:
1 #include <mach/spi.h> /* for mysoc_spi_data */ 2 3 /* if your mach-* infrastructure doesn't support kernels that can 4 * run on multiple boards, pdata wouldn't benefit from "__init". 5 */ 6 static struct mysoc_spi_data __initdata pdata = { ... }; 7 8 static __init board_init(void) 9 { 10 ... 11 /* this board only uses SPI controller #2 */ 12 mysoc_register_spi(2, &pdata); 13 ... 14 }
而特定的SOC平台的实用代码可能会像如下的样子:
1 #include <mach/spi.h> 2 static struct platform_device spi2 = { ... }; 3 void mysoc_register_spi(unsigned n, struct mysoc_spi_data *pdata) 4 { 5 struct mysoc_spi_data *pdata2; 6 7 pdata2 = kmalloc(sizeof *pdata2, GFP_KERNEL); 8 *pdata2 = pdata; 9 ... 10 if (n == 2) { 11 spi2->dev.platform_data = pdata2; 12 register_platform_device(&spi2); 13 14 /* also: set up pin modes so the spi2 signals are 15 * visible on the relevant pins ... bootloaders on 16 * production boards may already have done this, but 17 * developer boards will often need Linux to do it. 18 */ 19 } 20 ... 21 }
platform_data包含哪些内容具体看如何使用他们了,内置时钟还是外部时钟都有所区别.
5.2 声明Slave设备
第二类信息也是个列表:目标板上有哪种SPIslave设备,提供板级数据使设备能够正常工作.
arch/.../mach-*/board-*.c文件中有个小列表:列出了板载的SPI设备有哪些.这通常只有极少数.这个代码可能像如下这样:
1 static struct ads7846_platform_data ads_info = { 2 .vref_delay_usecs = 100, 3 .x_plate_ohms = 580, 4 .y_plate_ohms = 410, 5 }; 6 7 static struct spi_board_info spi_board_info[] __initdata = { 8 { 9 .modalias = "ads7846", 10 .platform_data = &ads_info, 11 .mode = SPI_MODE_0, 12 .irq = GPIO_IRQ(31), 13 .max_speed_hz = 120000 /* max sample rate at 3V */ * 16, 14 .bus_num = 1, 15 .chip_select = 0, 16 },
同样的,不论板载信息是什么,每个芯片可能需要多种类型.上面告诉我们这个设备的最大SPI时钟频率是120K,irq线是怎么连接的等等.
board_info中应该提供足够的信息使芯片驱动未加载前系统可以正常工作.最为麻烦的方面就是spi_device.mode中SPI_CS_HIGH位域了,在知道如何取消选择之前使和一个设备共享总线是不可能的.
你的板级初始代码会使用SPI设施来注册哪个table,当SPImastercontroller驱动注册完成后就可以使用了:
1 spi_register_board_info(spi_board_info,ARRAY_SIZE(spi_board_info));
正如其他的板级设置,你无需注销他们.
这被广发应用于卡式计算机中,如内存条,CPU,还有一些其他的板卡上小的东西也许仅仅只有30平方厘米.在这样的系统中,你的arch/.../mach-.../board-*.c文件将主要提供一些信息,关于插在这个主板上的设备的信息.这些信息当然包括能够通过卡槽连接的SPI设备了.
5.3 非静态设备
开发板相较于最终的产品扮演者不同的角色,比如潜在的热插拨SPI设备或控制器的需求.
在这种情形下你得使用spi_busnum_to_master()来查看busmaster,也可能使用spi_new_device()为热插拨板卡提供board_info信息.最后,你得手动的注销你注册过得资源:spi_unregister_device().
当linux通过SPI提供对MMC/SD/SDIO/DataFlash的支持时,所需的配置就是动态的了.幸运的是,这类设备都支持基础的设备标识符探测,因而他们是可以热插拨的.
6. 如何编写一个SPI Protocol Driver?
现在的大部分的SPI驱动都是内核空间的,也有支持用户空间的驱动.这里只涉及内核空间的驱动.
SPI Protocol 驱动有点像整合的platform驱动:
1 static struct spi_driver CHIP_driver = { 2 .driver = { 3 .name = "CHIP", 4 .owner = THIS_MODULE, 5 }, 6 7 .probe = CHIP_probe, 8 .remove = __devexit_p(CHIP_remove), 9 .suspend = CHIP_suspend, 10 .resume = CHIP_resume, 11 }; 12 13 };
驱动核心会自动尝试将这个驱动绑定到board_info中modalias域为CHIP的设备上.你的probe应该是这个样子的,除非你打算编写管理总线的代码:
1 static int __devinit CHIP_probe(struct spi_device *spi) 2 { 3 struct CHIP *chip; 4 struct CHIP_platform_data *pdata; 5 6 /* assuming the driver requires board-specific data: */ 7 pdata = &spi->dev.platform_data; 8 if (!pdata) 9 return -ENODEV; 10 11 /* get memory for driver's per-chip state */ 12 chip = kzalloc(sizeof *chip, GFP_KERNEL); 13 if (!chip) 14 return -ENOMEM; 15 spi_set_drvdata(spi, chip); 16 17 ... etc 18 return 0; 19 }
当进入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并初始化他们.
7. 我该如何编写SPI Masster Controller驱动呢?
一个SPI Controller可能会被注册在一个平台总线上.无论是哪条总线,都需要编写一个驱动去绑定这个设备(SPI Controller).要知道,一个SPI Controller对应一条总线,对应一个spi_master实例.
这类驱动最主要的任务就是提供一个spi_master.
使用spi_alloc_master()函数来为master分配内存,然后用spi_master_get_devdata()函数来为其私有数据分配内存.框架如下:
1 struct spi_master *master; 2 struct CONTROLLER *c; 3 4 master = spi_alloc_master(dev, sizeof *c); 5 if (!master) 6 return -ENODEV; 7 8 c = spi_master_get_devdata(master);
这个驱动当然要去初始化spi_master的各个字段,包括总线号(可能会和平台设备的ID相同)和三个方法(用来和SPI核心以及SPI protocol层交互),当然也会初始化其自身的内部状态.
当初始化完spi_master之后,就需要使用spi_register_master()函数来将其注册近系统.这时,SPI控制器的设备节点和任何之前预声明的SPI(从)设备就可以正常使用了,驱动模型核心层会将她们和其对应的驱动进行绑定.
如果你需要卸载SPI controller驱动,则调用spi_unregister_master()函数,这是和spi_register_master()函数对应的.
7.1 总线编号
总线编号是很重要的,因为Linux就是通过它来确定一个SPI总线的(再说一次,一个SPI总线对应一个SPI主控制器,就是spi_master,也对应一个总线编号).有效的总线编号是从零开始的.在SOC上,总线编号应该和芯片制造厂家定义的编号相同(这就是说产品ID).例如,硬件控制器SPI2的总线编号就是2.
SPI(从)设备就是通过总线编号来确定它是挂接在哪条总线上的(spi_board_info中定义).
如果你没有类似这样硬件厂商分配的总线编号,并且由于某种原因你不能分配到总线编号,那么就设定为负的总线编号.此时系统将会动态分配一个总线编号.不过,这样你就需要面对的是一个非静态的配置了.
设置SPI Masster的方法:
master->setup(struct spi_device *spi)
此方法将会设置SPI设备(SPI Masster当然也是设备)的时钟频率,SPI模式和字的大小.驱动程序可以更改由board_info结构确定的默认的值,然后调用spi_setup(spi)函数来使用新的配置.这个函数有可能会睡眠.除非每一个SPI(从)设备都有自己的配置寄存器,否则不要用上面的方法去改变SPI的配置.不然的话该驱动可能会损坏系统中正在其他SPI(从)设备上处理的IO操作.
** BUG ALERT: for some reason the first version of
** many spi_master drivers seems to get this wrong.
** When you code setup(), ASSUME that the controller
** is actively processing transfers for another device
(BUG警告:由于某种原因第一个版本的许多spi_master的驱动都会出现这种错误.当你运行setup()函数的时候,假定这个控制器正在处理另一个(从)设备的传输)
master->transfer(struct spi_device *spi, struct spi_message *message)
此函数不能睡眠,其功能是安排传输的发生并且调用complete()回调函数.这两个过程通常发生在其他的传输完成之后,当控制器处于空闲状态的时候.
master->cleanup(struct spi_device *spi)
您的控制器驱动可能会使用spi_device.controller_state来持有一个状态标志(这个状态是与设备的动态相关联的,也就是说这个字段标识了设备的当前状态).如果你这样做了,那么你必须提供一个cleanup()方法来释放这个状态标志.
7.2 SPI消息队列
大部分的驱动程序将要管理I/O队列给进transfer().
该队列可能仅仅是概念性的,例如,一个仅仅为礼品传感器访问的驱动可能会更好的工作于PIO.
但是队列可能会更真实一点,使用message->queue,PIO,DMA(特别的如果根文件系统是在SPI flash上的话).和IRQ的处理程序的上下文, tasklets,或者工作队列.你的驱动可以很花哨,或者很简单,根据你的需要了.这样的transfer()函数通常只是简单的将一个消息添加到一个队列上,然后开始一些异步传输引擎(除非它已经在运行了).
本文转自:https://www.kernel.org/doc/Documentation/spi/spi-summary