分类: LINUX
2013-05-21 17:45:24
------------
“串行外设接口”(SPI)是一个同步的四线串行链接用于微控制器连接传感器,内存和外设的。这是一个简单的“时点”的标准,获取一个标准化结构也不复杂。 SPI使用主/从配置。
三个信号线外加一个时钟(SCK,往往在10 MHz),并行数据线“主机输出,从机输入”(MOSI)或“主机输入,从机输出”(MISO)。数据交换有四种时钟模式:模式0和模式3最常用的。每个时钟周期输入和输出数据的数据;有一个数据位转移时时钟不会循环。并非所有的数据位都是这样,不是每个协议使用的全双工功能。
SPI的主机使用四线“chipselect”线激活一个指定的SPI从机设备,因此这三个信号线,可并行连接到几个芯片。所有的SPI从机都支持“chip select”;他们通常低电平有效,标志nCSx为从机'x'(如nCS0)。有些设备有其他的信号,通常包括一个主机中断。
不同于USB或SMBus串行总线,即使是低级别的SPI从机协议功能通常不能在供应商之通用。(除SPI存储器芯片的外)。
-SPI的可用于请求/响应式设备协议,例如触摸屏传感器和内存芯片。
- 它也可以被用来在某个方向的数据流(半双工),也可在同一时间使用(全双工)。
- 某些设备可能使用8位字。其他可能不同的字长,如12位或20位数字采样流。
- 一个字通常是大端(MSB)发送,但有时也会用小端发送(LSB)。
- 有时SPI是用于菊花链(daisy-chain)设备,如移位寄存器。
以同样的方式,SPI从机将很少的支持任何一种自动发现/枚举协议。一个给定的SPI主机的从设备访问树通常会使用配置表手动设置。
SPI是类似四线协议所使用的唯一名称,大多数控制器都可以处理“MicroWire”(认为它是处理半双工SPI,请求/响应协议),SSP(“同步串行协议“),PSP(”可编程串行协议“),及其他相关协议。
有些芯片把MOSI和MISO信号线组合起来,限制自己硬件在半双工水平工作。其实一些SPI芯片有信号模式用作捆绑的选项。这些可以为SPI使用相同的编程接口,当然,他们不会处理全双工传输。你可能会发现有些芯片描述使用“三线”信号:SCK,data,nCSx。(数据线有时被称作MOMI或SISO。)
微控制器通常支持SPI的主机和从机双方协议。本文档(和Linux)目前只支持主机方SPI的相互作用。
谁在使用它?什么样系统?
---------------------------------------
Linux开发人员使用SPI很可能为嵌入式系统板编写设备驱动。 SPI用来控制外部芯片,它也是一个支持MMC或SD记忆卡的协议。(旧的“DataFlash”卡,早期MMC,但使用相同的连接器和卡的形状,只支持SPI)。一些PC硬件的BIOS代码使用SPI闪存存储。
SPI从芯片用于传感器和编解码器的数字/模拟转换器芯片,内存,外围设备,如USB控制器或以太网适配器等等。
大多数系统采用SPI整合主板上的一些设备。
一些SPI链接作为扩展连接,在没有专用SPI控制器的情况下, GPIO管脚可以用来创建一个低速“bitbanging”适配器。很少有系统支持“hotplug”的SPI;主要的原因是使用SPI的目的是低成本和易操作,如果动态重新配置很重要重要,USB接口往往会是一个更适当的低引脚数的外设总线。
许多微控制器使用SPI模式可以运行Linux集成一个或多个I / O的接口。由于SPI支持,他们可以使用MMC或SD卡无需特殊用途的MMC / SD / SDIO控制器卡。
我很困惑。什么是这四个SPI时钟模式?
-----------------------------------------------------
它很容易被迷惑在这里,制造商的文档你会发现不一定是有用的。四种模式结合了两种模式位:
-CPOL位表示初始时钟极性。 CPOL = 0表示时钟初始为低电平,所以开始的第一(leading)边缘是上升沿,第二边缘(trailing)是下降沿。 CPOL = 1时的时钟启动为高电平,所以第一(leading)的边缘是下降沿。
-CPHA的指示用于采样数据的时钟相位; CPHA = 0时表示边沿超前,CPHA = 1表示边沿滞后。
由于信号在采样前需要稳定下来,CPHA = 0时意味着其数据写入在第一个时钟边沿之前半个时钟。片选将使其可用。
芯片手册不会总是说:“使用SPI模式X”,但其时序图会指明CPOL和CPHA模式。
在SPI模式号,CPOL位是高位和CPHA低位。所以,当芯片的时序图显示的时钟
起始低(CPOL = 0时),在下降沿数据稳定采样(CPHA = 1),这是SPI模式1。
请注意,片选一但有效时钟模式就会相关。所以主机必须在选择一个从机前使时钟无效。当片选有效时,从机可以通过时钟电平告诉采样选择极性。这就是为什么许多设备例如支持模式0和3:他们不关心极性,和总是在输入/输出时钟数据在时钟上升沿。
这些驱动程序编程接口如何工作?
------------------------------------------------
SPI的请求总是进入I / O队列。一个给定的SPI设备的请求总是在FIFO顺序执行,通过回调并异步完成。这些调用也有一些简单的同步封装,包括一些常见的传输类型,例如写一个命令然后,然后读取其响应。
有两种类型的SPI驱动程序,这里所涉及的:
控制器的驱动程序...控制器可以内置在系统芯片,并经常支持Master和Slave的角色。
这些驱动触摸硬件寄存器,可以使用DMA。或者也可以的PIO bitbangers,只是需要的GPIO引脚。
协议驱动程序...这些传递消息一个SPI连接的主机和从机通过控制驱动通讯传递消息。
因此,例如一个协议驱动程序,可能让MTD层导出数据到SPI flash上的文件系统进行存储;
其他也开一用来控制音频接口,目前触摸屏传感器作为输入接口,或监测的工业加工过程中的温度和电压水平。和那些可能都共享相同的控制器驱动程序。
“struct spi_device”封装是主机编程接口。在写这篇文章时,Linux有没有从机编程接口。
SPI编程接口有一个最低限度的核心,侧重于使用驱动模型连接控制器和协议驱动程序,使用设备表板特定的初始化代码。 SPI显示在sysfs中出现的地点:
/sys/devices/.../CTLR ...对于一个给定的SPI控制器的物理节点
/sys/devices/.../CTLR/spiB.C ... spi_device在总线“B”,片选C,通过CTLR访问。
/sys/bus/spi/devices/spiB.C ...符号链接到该物理.../CTLR/spiB.C device设备
/sys/devices/.../CTLR/spiB.C/modalias...设备所使用的确定驱动(forhotplug/coldplug)
/sys/bus/spi/drivers/D ...一个或多个spi*.*设备的驱动程序
/sys/class/spi_master/spiB ...符号链接(或实际设备节点)到一个逻辑节点,可容纳控制器管理总线“B”的类相关状态的。所有spiB.*设备使用SCLK,MOSI和MISO共用一个物理SPI总线段。
注意控制器的类状态的实际位置取决于您是否启用CONFIG_SYSFS_DEPRECATED。在这个时候,只有特定类状态是总线号("B"in "spiB"),所以那些/sys/class的条目只有快速识别总线时有用。
特定板初始化代码如何声明SPI器件?
------------------------------------------------------
Linux需要多种信息才能正确配置SPI器件。这些信息通常是电路板的特定代码,甚至有些芯片支持自动发现/枚举。
声明控制器
第一类信息是存在的SPI控制器的清单。由于片上系统(SOC)在主板上,这些通常是平台设备,控制器可能需要一些platform_data正常运行。 “结构platform_device”将包括资源如控制器的第一个寄存器的物理地址和其IRQ。
平台经常抽象为“register SPI controller”操作,
也许代码以初始化引脚配置与耦合,使
arch/.../mach-*/board-*.c的多个主板的文件都可以共享基本相同的控制器设置代码。这是因为大多数的SOCs有几个SPI功能的控制器,只在一个实际可用的指定主板上一般设置和注册。
例如 arch/.../mach-*/board-*.c (个人理解这个文件应该是arch/.../mach-*/mach-*.c)文件可能包含下面代码:
#include
/* if your mach-* infrastructure doesn'tsupport kernels that can
* runon multiple boards, pdata wouldn't benefit from "__init".
*/
static struct mysoc_spi_data __initdatapdata = { ... };
static __init board_init(void)
{
...
/* this board only uses SPI controller#2 */
mysoc_register_spi(2, &pdata);
...
}
SOC-specific单元代码可能会如下:
#include
static struct platform_device spi2 = { ...};
void mysoc_register_spi(unsigned n, structmysoc_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 ... bootloaderson
* production boards may already have donethis, but
* developer boards will often need Linux to doit.
*/
}
...
}
注意即使使用相同的SOC控制器platform_data可能会有所不同, 。例如,在一个板的SPI可能会使用外部时钟,另一个设备SPI时钟来自当前设置的主时钟。
声明从设备
第二类信息是在目标板上存在的SPI从器件列表,往往需要一些特定板的数据驱动程序才能正常工作。
通常情况下,您的arch/.../mach-*/board-*.c(个人理解这个文件应该是arch/.../mach-*/mach-*.c)文件将提供一个小列表,列出每块板的SPI设备。 (这通常是只有一个一小段),可能看起来像如下。
static structads7846_platform_data ads_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 sample rate at 3V */ * 16,
.bus_num = 1,
.chip_select = 0,
},
};
再次,注意特定板提供的信息,每个芯片可能需要几种类型。这个例子显示了泛型约束,如最快的SPI时钟允许(在这种情况下,一个板上的电压功能)或IRQ管脚是导线,加上芯片的具体限制,例如在一个引脚的电容变化产生的一个重要的延迟。
(还有的“controller_data”,这可能是控制器驱动程序的有用信息。一个例子是特定外设的DMA调整数据或片选回调。这是存储在spi_device后。)
board_info应提供足够的信息,让系统在没有芯片的驱动程序被加载时工作。最麻烦的方面比如在spi_device.mode域的SPI_CS_HIGH位,因为设备共享一个总线,直到底层结构直到如何取消选择它前不可能中断片选"backwards"。
那么你的板初始化代码将使用SPI底层结构登记表,所以它是,SPI主控制器驱动被注册后晚些时候推出时:
spi_register_board_info(spi_board_info,ARRAY_SIZE(spi_board_info));
类似其他静态特定板的设置,你不需注销。
广泛使用的“card”的风格电脑封装内存,CPU,在卡上并没有别的,也许只有30平方厘米。在这样的系统上,
arch/.../mach-.../board-*.c文件将主要提供卡插入主板上的设备信息,
当然包括通过卡连接器挂上的SPI设备!
非静态配置
开发板经常扮演不同的角色,比如一个例子是潜在需要热插拔的SPI设备和/或控制器。
对于这些情况下,你可能需要使用spi_busnum_to_master()来查找了SPI总线主机,还可能需要spi_new_device()提供热插拔的主板信息。当然,在主板被删除时需要调用spi_unregister_device()。
当Linux通过SPI包含支持MMC / SD / SDIO /的DataFlash卡配置也将是动态的。幸运的是,所有这些设备的支持基于设备识别探测,所以他们通常可以热插拔。
我怎样写“SPI协议驱动程序”?
----------------------------------------
目前大多数SPI驱动程序是当前内核驱动程序,但也有支持用户空间的驱动程序。在这里,我们只讲内核驱动程序。
SPI协议驱动程序有点像平台的设备驱动程序:
static struct spi_driverCHIP_driver = {
.driver = {
.name = "CHIP",
.owner = THIS_MODULE,
},
.probe =CHIP_probe,
.remove =__devexit_p(CHIP_remove),
.suspend =CHIP_suspend,
.resume =CHIP_resume,
};
驱动核心尝试自动将此驱动程序绑定到任何支持SPI的设备board_info分配一个代号“CHIP”。您的probe()代码可能看起来是这样,除非你创建一个设备用于管理总线(出现在/sys/class/spi_master)。
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设备使用“结构spi_message”。当remove()返回,或probe()失败后,驱动保证它不会提交任何此类消息。
- 一个spi_message是协议操作的序列,执行作为一个原子序列。 SPI驱动控制包括:
+当双向读取和写入启动... spi_transfer请求的队列如何安排;
+ 哪些I / O缓冲区被占用... 每个spi_transfer封装一个传输方向的缓冲区,支持全双工(两个指针,也许在这两种情况下相同)及半双工(一个指针为NULL)传输;
+传输后可选定义短暂延迟... 使用spi_transfer.delay_usecs设置;
+传输和任何延误后片选是否变得无效... 使用spi_transfer.cs_change标志;
+提示是否接下来的消息是可能去此相同设备...
在最后传输的原子操作使用spi_transfer.cs_change标志,为芯片取消选择和选择操作潜在的节约成本。
- 按照标准内核规则,并在你的消息中提供DMA安全缓冲区。这种方式使用DMA控制器驱动程序不会被强迫作出额外的副本除非硬件需要(如围绕硬件勘误表工作,强制使用反弹缓冲)。
如果的标准dma_map_single()处理这些缓冲区不适当,你可以使用spi_message.is_dma_mapped,告诉控制器驱动程序你已经提供了相关的DMA地址。
- 基本的I / O原始会是spi_async()。异步请求可能
在任何情况下产生(IRQ处理程序,任务等),完成报告使用消息回调产生。任何检测到的错误后,该芯片被取消和处理spi_message被中止。
- 也有同步的封装例如spi_sync(),和封装spi_read中,spi_write(),spi_write_then_read()。这些可发出只有在上下文中可能睡眠,他们都简洁(小,和“可选”)层超过spi_async()。
-spi_write_then_read()调用,方便封装其中,应该只用少量的数据额外副本的消耗可能会被忽略。它的设计支持常见的RPC风格的要求,比如写一个8位命令读一个十六位的响应 - spi_w8r16()是其中封装之一,执行这些操作。
Somedrivers may need to modify spi_device characteristics like the transfer mode,wordsize, or clock rate. This is done with spi_setup(),which would normally becalled from probe() before the first I/O is done to the device. However, thatcan also be called at any time that no message is pending for that device.
虽然“spi_device”将是驱动的底部边界,上边界可能包括sysfs(尤其是传感器读数)输入层,ALSA,网络、MTD字符设备框架,或其它Linux子系统。
注意,有两种类型的内存,是你的驱动必须管理的一部分与SPI器件相互作用。
- I / O缓冲器使用通常的Linux规则,而且必须是DMA-safe。你通常从堆或空白页面池分配。不要使用堆栈,或任何被声明为“静态”。
- spi_message和spi_transfer元数据用于结合I/O缓冲成一组协议交换的I / O缓冲区。这些可以被很方便分配到任何地方,包括部分其他分配一次的驱动器数据结构,初始化为零。
如果你喜欢,spi_message_alloc()和spi_message_free()可用来分配和零初始化spi_message。
我怎样写“SPI主控制器驱动程序”?
-------------------------------------------------
一个SPI控制器可能会被注册上platform_bus;写驱动程序绑定到设备,两者涉及总线。
这种类型的驱动程序的主要任务是提供“spi_master”。使用spi_alloc_master()分配主机,和spi_master_get_devdata()获取分配给该设备的
driver-private数据。
struct spi_master *master;
struct CONTROLLER *c;
master = spi_alloc_master(dev, sizeof *c);
if (!master)
return-ENODEV;
c = spi_master_get_devdata(master);
驱动程序将初始化spi_master,包括总线数量(可能与平台设备ID相同)和三个方法
用于核心与SPI和SPI协议驱动程序交互。它将同时初始化它自己的内部状态。 (见下文有关总线编号和方法。)
你的spi_master初始化后,然后使用spi_register_master()它发布到系统的其余部分。在那个时候,设备节点控制器和任何预先声明的SPI设备将可用,驱动程序模型的核心将它们绑定到驱动。
如果您需要删除您的SPI控制器驱动,spi_unregister_master()将扭转的spi_register_master()的效果。
总线编号
总线编号很重要,因为这是Linux如何确定一个给定的SPI总线(共享的SCK,MOSI,MISO)的。有效总线从零开始。SOC系统上总线的数字应该匹配的芯片制造商定义的数字。例如,硬件控制器SPI2的总线号2,spi_board_info将使用该号码连接到它的设备。
如果你没有这样的硬件分配总线号,由于某种原因你不能分配给他们,然后提供一个负的总线数。这将被替换为一个动态分配的号码。然后,你需要把它作为一个非静态的配置(见上文)。
SPI主方法
master->setup(structspi_device *spi)
这台设备的时钟速率,SPI模式,和字的大小。驱动可能改变由board_info提供的默认值,然后调用spi_setup(SPI),调用这个例程。它可以休眠。
除非每个SPI从机都有其自己的配置寄存器,否则不要马上改变他们...不然可能损坏驱动器的I / O在其他SPI器件的运行。
**错误警告:由于某种原因,第一个版本 spi_master许多驱动似乎得到这个错误。当你的代码setup(),假设控制器正在进行处理其他设备的传输。**
master->transfer(structspi_device *spi, struct spi_message *message)
这不能睡眠。其职责是安排传送,并发出它的complete()回调。这些通常会发生后,其他传输完成前,如果控制器处于闲置状态,它需要被启动。
master->cleanup(structspi_device *spi)
您的控制器的驱动程序可能会使用spi_device.controller_state保持状态,这与设备动态联合。如果你这样做,请务必提供cleanup()方法来释放该状态。
SPI消息队列
大部分驱动将通过transfer()管理的I / O队列反馈。
该队列可以是纯粹的概念。例如,一驱动只用低频传感器访问,可能是使用同步的PIO。(匪夷所思的一句话。。。)
但队列可能会是非常现实的,利用message->queue,PIO,DMA(尤其是如果根文件系统是在SPI闪存),和执行上下文如IRQ处理,tasklet,或工作队列(例如keventd)。您的驱动程序,可以花哨,或简单,因为你需要。像transfer()方法通常只需要添加到的消息队列中,然后启动一些异步传输引擎(除非它是已经运行的)。