特定板初始化代码如何声明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 <mach/spi.h> /* for mysoc_spi_data */
/* 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 <mach/spi.h>
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。