Chinaunix首页 | 论坛 | 博客
  • 博客访问: 99402
  • 博文数量: 35
  • 博客积分: 10
  • 博客等级: 民兵
  • 技术积分: 147
  • 用 户 组: 普通用户
  • 注册时间: 2012-11-05 22:10
个人简介

不经历风雨,怎能见彩虹!

文章分类

全部博文(35)

文章存档

2013年(35)

我的朋友

分类: LINUX

2013-05-21 17:45:24

什么是SPI?

------------

“串行外设接口”(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:他们不关心极性,和总是在输入/输出时钟数据在时钟上升沿。

 

 


这些驱动程序编程接口如何工作?

------------------------------------------------

头文件包括kerneldoc,你一定要读内核API文档的本章。这仅仅是一个概述,在了解这些细节之前使您得到大概的了解。

 

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    /* 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

 

    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()方法通常只需要添加到的消息队列中,然后启动一些异步传输引擎(除非它是已经运行的)。


阅读(1889) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~