什么是SPI?
“串行外围接口”是一个同步的四线制串行线,用于连接微控制器和传感器、存储器及外围设备。三条信号线持有时钟信号(SCLK,经常在10MHz左右)和并行数据线带有“主出,从进(MOSI)”或是“主进,从出(MISO)”信号。数据交换的时候有四种时钟模式,模式0和模式3是最经常使用的。每个时钟周期将会传递数据进和出。如果没有数据传递的话,时钟将不会循环。
SPI主设备使用“片选”线来使一个给定的SPI从设备工作,所以那三条信号线可能并行地连接若干个芯片。所有的SPI从设备都支持片选。一些设备有其它信号,通常包括给主设备的中断。
不像例如USB、SMBUS之类的串行线,甚至SPI从功能的低层协议在不同厂家之间都不是通用的(除了SPI存储芯片之类的)。
---SPI可用于要求/答复类型的设备协议,例如触摸屏传感器和存储芯片。
---它也可以用于在每个方向传递数据(半双工),或是同时双向传递(全双工)。
---一些设备可以使用8比特字节。其它可以使用不同的字节长度,例如12比特或是20比特的数字采样。
同时,SPI从设备基本不支持任何自动发现/列举的协议。一个指定SPI主设备可以获得从设备树,这种树通常是根据配制表手工建立的。
SPI仅仅是那些四线制协议使用的一个名字,大多数的控制器很容易处理“微线”(可认为是一种半双工的SPI,用于要求/答复协议),SSP(同步串行协议),PSP(可编程串行协议)和其它相关协议。
微控制器通常都支持SPI协议的主、从双方。这篇文档(Linux)目前仅仅支持SPI交互的主的一方。
谁使用它?在什么系统上?
使用SPI的Linux开发者可能是为嵌入式系统的板子写设备驱动。SPI用于控制外部芯片,它也是一种可以控制MMC或SD存储卡的协议(老的DataFlash卡,是MMC的前身,使用同样的连接器和卡形状,仅仅支持SPI)。一些PC硬件为BIOS代码使用SPI闪存。
SPI从设备包括用于模拟传感器,编解码的数字/模拟转换器,例如USB控制器的外围设备,以太网适配器等等。
大多数系统在一个主板上使用SPI连接一些设备。一些提供在扩展连接器上的SPI连接。例如在没有特定SPI控制器存在,GPIO引脚就被用于产生一个低速的“bitbanging”适配器。很少有系统能热拔插SPI控制器。使用SPI的原因主要是低成本和简单操作。如果动态配置非常重要的话,USB是一种更适合的低引脚数的外围总线。
许多微控制器能够以SPI模式集成一个或多个I/O接口来运行Linux。若给定SPI支持,就可以不需要特定的MMC/SD/SDIO控制器来使用MMC或SD卡。
这些驱动编程接口是怎样工作的呢?
<linux/spi/spi.h>头文件包括内核文档,也包括主要的源代码,必须读它。这仅仅是一个总体概述,所以必须在弄懂细节之前获得一个整体印象。
SPI通常要求进入I/O队列。要求一个指定的SPI设备以FIFO顺序执行,然后以完成回调来异步完成。也有一些简单的同步操作来完成这些调用,包括例如写命令然后读回复的普通处理类型。
有两种类型的SPI驱动,被称为:
控制器驱动:它们通常内嵌于片上系统处理器,通常既支持主设备,又支持从设备。这些驱动涉及硬件寄存器,可能使用DMA。或它们使用GPIO引脚成为PIO bitbangers。
协议驱动:它们通过控制器驱动,以SPI连接的方式在主从设备之间传递信息。
所以例如一个协议驱动可能告诉MTD层把数据送到存储在SPI闪存如DataFlash上的文件系统内。其它可能控制音频接口,提供触摸屏传感器作为输入接口,或是在工业处理过程中监控温度、电压水平。它们也可能共用同样的控制器驱动。
struct spi_device封装了那两种驱动的主方接口。这次写的,Linux没有从方编程接口。
有一个最小的SPI编程接口内核,主要使用驱动模型连接控制器和协议驱动,这些驱动使用由特定板初始化代码提供的设备表。SPI出现在sysfs中几个地方:
/sys/devices/.../CTLR/spiB.C —— spi_device在总线“B”上,片选是C,通过CTLR操作。
/sys/devices/.../CTLR/spiB.C/modalias ——区别这个设备(热拔插或冷拔插)使用的驱动。
/sys/bus/spi/devices/spiB.C —— 实际spiB-C设备的符号连接。
/sys/bus/spi/drivers/D ——一个或多个spi设备使用的驱动。
/sys/class/spi_master/spiB ——管理总线“B”的控制器的类设备。所有spiB.*设备用SCLK, MOSI和MISO控制同样的物理SPI总线部分。
特定板的初始化代码怎样声明SPI设备?
Linux使用几种信息来正确配置SPI设备。这些信息通常是由特定板代码提供,甚至是用于支持一些自动发现/列举的芯片。
声明控制器
第一种信息是指明SPI控制器怎样存在。对基于片上系统的(SOC)板,它们经常是平台设备,控制器使用platform_data来使操作正常运行。"struct platform_device"包括例如控制器第一个寄存器的物理地址和它的中断。
平台经常抽象“注册SPI控制器”的操作,也许用代码初始化引脚配置,所以一些板的arch/.../mach-*/board-*.c文件都使用同样的基本的控制器建立代码。这就是大多数片上系统支持几个SPI控制器的原因,只有那些在一个指定板上的控制器才能正常的建立和注册。
所以例如arch/.../mach-*/board-*.c文件有如下代码:
#include <asm/arch/spi.h> /* for mysoc_spi_data */
/* if your mach-* infrastructure doesn\'t support kernels that can
* run on multiple boards, pdata wouldn\'t benefit from "__init".
*/
static struct mysoc_spi_data __init pdata = { ... };
static __init board_init(void)
{
...
/* this board only uses SPI controller #2 */
mysoc_register_spi(2, &pdata);
...
}
特定片上系统有用代码像这样:
#include <asm/arch/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);
/* als set up pin modes so the spi2 signals are
* visible on the relevant pins ... bootloaders on
* production boards may already have done this, but
* developer boards will often need Linux to do it.
*/
}
...
}
注意板上的platform_data可能不同,即使是使用同样的SOC控制器。例如,在一块板上,SPI使用外围时钟,而另外一块从当前配置的主时钟上获取SPI时钟。
声明从设备
第二种类型的信息是指明在目标板上SPI从设备怎样存在,通常是用一些驱动正常工作所需要的特定板设备数据。
正常情况下arch/.../mach-*/board-*.c文件提供在每个板上的SPI设备列出的小的表结构。(这可能是非常少的一部分)。代码大致如下:
static struct ads7846_platform_data ads_info = {
.vref_delay_usecs = 100,
.x_plate_ohms = 580,
.y_plate_ohms = 410,
};
static struct spi_board_info spi_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比特位,因为和一个后来解析片选的设备共用总线是不可能的。
然后你的板初始化代码用SPI基础结构初始化表结构,所以在后来SPI主控制器设备注册时也是可用的。
spi_register_board_info(spi_board_info, ARRAY_SIZE(spi_board_info));
像其它静态特定板建立一样,不会注销它们。
广泛使用的“卡”类型电脑把存储器、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驱动当前都是内核驱动。一个用户空间的驱动API是另一个内核驱动,可能通过aio_read(), aio_write(), and ioctl()调用提供一些底层存取,使用标准的用户空间sysfs机制来绑定一个给定的SPI设备。
SPI协议驱动有点类似平台设备驱动:
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给定别名为"CHIP"的SPI设备。probe()看上去像这样,除非你在创建一个class_device:
static int __devinit CHIP_probe(struct spi_device *spi)
{
struct CHIP *chip;
struct CHIP_platform_data *pdata;
/* assuming the driver requires board-specific data: */
pdata = &spi->dev.platform_data;
if (!pdata)
return -ENODEV;
/* get memory for driver\'s per-chip state */
chip = kzalloc(sizeof *chip, GFP_KERNEL);
if (!chip)
return -ENOMEM;
spi_set_drvdata(spi, chip);
... etc
return 0;
}
一旦进入probe(),驱动使用"struct spi_message"向SPI设备要求I/O。当remove()返回时,驱动保证将不会提交任何这种信息。
一个spi_message是协议操作序列,以一个原子序列执行。SPI驱动控制包括:
(1)当双向读写开始,是根据spi_transfer要求序列是怎样安排的。
(2)随意设定传递后的短延时,使用spi_transfer.delay_usecs设定。
(3)在一次传递和任何延时之后,无论片选是否活跃,使用spi_transfer.cs_change标志,暗示下条信息是否进入这个同样的设备,使用原子组中最后一次传输上的spi_transfer.cs_change标志位,可能节省芯片选择取消操作的成本。
遵循标准内核规则,在信息中提供DMA安全操作的缓冲区。使用DMA的控制器驱动采用这种方法主要不是为了做多份拷贝,除非硬件需要(例如,工作在强调反复缓冲使用的硬件正误表)。
如果标准的dma_map_single()处理这些缓冲不合适,可以使用spi_message.is_dma_mapped告诉已经提供相应DMA地址的控制器驱动。
基本的I/O原语是spi_async()。Async请求可在任何上下文(irq处理,任务等等)中提出,使用消息提供的回调函数报告完成。在检测出任何错误之后,芯片将被取消选定,这条spi_message的处理将退出。
也有一些同步封装函数例如spi_sync(),封装函数如spi_read(), spi_write()和spi_write_then_read()。这些可能仅仅在要休眠的上下文中调用,它们都是spi_async()上干净(并且小,可选择)的层。
spi_write_then_read()调用,是它附近方便的封装,仅仅在额外拷贝的成本可以被忽略的情况下和少数数据一起使用时被调用。它用来支持普通的RPC类型的请求,例如写一个8比特的请求,读一个16比特的答复——spi_w8r16()是一个封装,确实做这样的操作。
一些驱动需要修改spi_device特征,例如传输模式、字长或时钟速率。这个可以用spi_setup()完成,在设备发出第一个I/O后,由probe()调用。
当"spi_device"是驱动的底层边界时,上层边界将包括sysfs(尤其是给传感器读)、输入层、ALSA、网络、MTD、字符设备结构或者其它Linux子系统。
注意,驱动中必须管理两种类型的存储设备做为和SPI设备交互的一部分。
I/O缓冲使用通用的Linux规则,必须是DMA安全的。通常使用堆或自由页池中分配。不要使用栈以及任何声明为“static”的方法。
spi_message和spi_transfer的元数据用来粘合I/O 缓冲为一组协议交互。它们可以在方便的地方任意分配,包括其它一次分配的驱动数据结构,初始化为0。
如果你喜欢,spi_message_alloc()和spi_message_free()函数可以很方便地用一些传递来分配和初始化一个spi_message。
怎样写一个SPI主控制器驱动呢?
一个SPI控制器可能在platform_bus上注册,写一个驱动来绑定设备,不管使用哪种总线。这种类型的驱动的主要工作是提供一个"spi_master"。使用spi_alloc_master()分配主设备,class_get_devdata()来获得分配给设备的唯一的驱动数据。
struct spi_master *master;
struct CONTROLLER *c;
master = spi_alloc_master(dev, sizeof *c);
if (!master)
return -ENODEV;
c = class_get_devdata(&master->cdev);
驱动初始化spi_master里的字段,包括总线数(可能和平台设备ID相同)和三种用于和SPI核心及SPI协议驱动交互的三种方法。它也会初始化自己的内部状态。(看下面关于总线编号方式和那些方法)。
在初始化spi_master之后,使用spi_register_master()公开到系统的其余部分。这一次,控制器的设备节点和任何先前声明的spi设备将可用了,设备模型内核将会很小心的绑定它们到驱动上。
如果需要移除SPI控制器驱动,spi_unregister_master()将做spi_register_master()相反的操作。
总线编号方式
总线编号方式很重要,因为这是Linux怎样区分一个给定的SPI总线(共享的SCK、MOSI、 MISO)。有效的总线编号从0开始。在SOC系统上,总线编号应该匹配芯片制造商定义的编号。例如,硬件控制器SPI2将是总线编号为2,连接它的设备的spi_board_info将使用这个编号。
如果没有这样硬件指定的总线编号,很可能是你不能指定它,然后提供了一个负数的总线编号。然后由一个动态分配的编号取代。然后需要将他当成一个非静态配置(参考上面)。
SPI主设备方法
master->setup(struct spi_device *spi)
这个操作设置了设备时钟频率,SPI模式和字节长度。驱动将改变由board_info提供的默认值,然后调用spi_setup(spi)来唤醒这个函数。它可能休眠。
master->transfer(struct spi_device *spi, struct spi_message *message)
这个操作不会休眠。它的任务是安排传递发生和complete()调用的引用。这两个操作将在后面正常发生,在其它传递完成之后。
master->cleanup(struct spi_device *spi)
你的控制器驱动可以使用spi_device.controller_state来保持和这个设备相关的状态。如果你这么做了,确保提供cleanup()方法来释放这个状态。
SPI消息队列
驱动大小将管理transfer()需要的I/O队列。这个队列完全是概念上的。例如,一个仅仅是用于低频传感器存取的驱动可能使用同步PIO时很正常。
但是队列也很可能很真实,使用message->queue, PIO,经常DMA(尤其是根文件系统在SPI闪存上),例如IRQ处理、任务队列或工作队列(例如keventd)等的这行上下文中。你的驱动可以像你想象的那样简单。例如一个transfer()方法可正常把信息加入到队列中,然后开始一些异步的传输引擎(除非已经在运行了)。