Linux内核SPI支持概述

2012年2月2日

什么是SPI?

“串行外设接口”(SPI)是一种同步的四线串行链路,用于将微控制器连接到传感器、存储器和外设。它是一个简单的“事实上的”标准,不够复杂到需要一个标准化机构。SPI使用主/从配置。

三根信号线包含一个时钟(SCK,通常在10 MHz左右)和并行数据线,具有“主出从入”(MOSI)或“主入从出”(MISO)信号。(也使用其他名称。)有四种时钟模式,通过这些模式交换数据;模式0和模式3是最常用的。每个时钟周期移出数据并移入数据;只有当有数据位要移位时,时钟才会循环。但并非所有数据位都被使用;并非每个协议都使用这些全双工功能。

SPI主机使用第四根“片选”线来激活给定的SPI目标设备,因此这三根信号线可以并行连接到多个芯片。所有SPI目标都支持片选;它们通常是低电平有效的信号,标记为目标“x”的nCSx(例如nCS0)。一些设备有其他信号,通常包括到主机的中断。

与USB或SMBus等串行总线不同,即使是SPI目标功能的低级协议,通常在供应商之间也不可互操作(除了SPI存储芯片等商品)。

  • SPI可以用于请求/响应式设备协议,例如触摸屏传感器和存储芯片。

  • 它也可以用于在任一方向(半双工)或同时在两个方向(全双工)流式传输数据。

  • 一些设备可能使用8位字。其他设备可能使用不同的字长,例如12位或20位数字采样的流。

  • 字通常以其最高有效位(MSB)优先发送,但有时最低有效位(LSB)优先发送。

  • 有时SPI用于菊花链连接设备,例如移位寄存器。

同样,SPI目标很少支持任何类型的自动发现/枚举协议。从给定的SPI主机控制器可访问的目标设备的树通常是手动设置的,带有配置表。

SPI只是这种四线协议使用的名称之一,大多数控制器都可以毫无问题地处理“MicroWire”(可以将其视为用于请求/响应协议的半双工SPI)、SSP(“同步串行协议”)、PSP(“可编程串行协议”)和其他相关协议。

一些芯片通过组合MOSI和MISO来消除信号线,并将自己限制在硬件级别的半双工。事实上,一些SPI芯片将这种信号模式作为跨接选项。可以使用与SPI相同的编程接口访问这些芯片,但当然它们不能处理全双工传输。您可能会发现这些芯片被描述为使用“三线”信令:SCK、数据、nCSx。(该数据线有时称为MOMI或SISO。)

微控制器通常支持SPI协议的主机端和目标端。本文档(和Linux)支持SPI交互的主机端和目标端。

谁使用它?在什么类型的系统上?

使用SPI的Linux开发人员可能正在为嵌入式系统板编写设备驱动程序。SPI用于控制外部芯片,它也是每个MMC或SD存储卡支持的协议。(较旧的“DataFlash”卡,早于MMC卡,但使用相同的连接器和卡形状,仅支持SPI。)一些PC硬件使用SPI闪存来存储BIOS代码。

SPI目标芯片的范围从用于模拟传感器和编解码器的数字/模拟转换器,到存储器,到USB控制器或以太网适配器等外设;以及更多。

大多数使用SPI的系统将在主板上集成一些设备。一些在扩展连接器上提供SPI链路;在没有专用SPI控制器的情况下,可以使用GPIO引脚来创建低速“位敲击”适配器。极少数系统会“热插拔”SPI控制器;使用SPI的原因集中在低成本和简单操作上,如果动态重新配置很重要,USB通常是更合适的低引脚数外设总线。

许多可以运行Linux的微控制器集成了具有SPI模式的一个或多个I/O接口。有了SPI支持,它们就可以使用MMC或SD卡,而无需专用的MMC/SD/SDIO控制器。

我感到困惑。这四种SPI“时钟模式”是什么?

很容易在这里感到困惑,而且您会发现的供应商文档不一定有帮助。这四种模式结合了两个模式位

  • CPOL指示初始时钟极性。CPOL=0表示时钟从低电平开始,因此第一个(前沿)是上升沿,第二个(后沿)是下降沿。CPOL=1表示时钟从高电平开始,因此第一个(前沿)是下降沿。

  • CPHA指示用于采样数据的时钟相位;CPHA=0表示在前沿采样,CPHA=1表示在后沿采样。

    由于信号需要在采样前稳定下来,因此CPHA=0意味着其数据在第一个时钟沿之前半个时钟写入。片选可能使其变为可用。

芯片规格不一定会以这么多字说“使用SPI模式X”,但它们的时序图会使CPOL和CPHA模式清晰。

在SPI模式编号中,CPOL是高位,CPHA是低位。因此,当芯片的时序图显示时钟从低电平开始(CPOL=0)并且数据在后沿时钟沿(CPHA=1)期间稳定以进行采样时,那是SPI模式1。

请注意,一旦片选变为有效,时钟模式就相关了。因此,主机必须在选择目标之前将时钟设置为非活动状态,并且目标可以通过在其选择线变为有效时采样时钟电平来判断所选极性。这就是为什么许多设备支持例如模式0和模式3:它们不关心极性,并且始终在上升时钟沿时钟输入/输出数据。

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

<linux/spi/spi.h> 头文件包括 kerneldoc,主要源代码也包括,您肯定应该阅读内核 API 文档的该章节。这只是一个概述,因此您可以在了解这些细节之前获得全局概念。

SPI请求始终进入I/O队列。给定SPI设备的请求始终按FIFO顺序执行,并通过完成回调异步完成。对于这些调用,还有一些简单的同步包装器,包括用于常见事务类型(如写入命令然后读取其响应)的包装器。

有两种类型的SPI驱动程序,这里称为

控制器驱动程序 ...

控制器可以构建到片上系统处理器中,并且通常支持控制器和目标角色。这些驱动程序触摸硬件寄存器并可能使用DMA。或者它们可以是PIO位敲击器,只需要GPIO引脚。

协议驱动程序 ...

这些驱动程序通过控制器驱动程序传递消息,以便与SPI链路另一端的目标或控制器设备通信。

因此,例如,一个协议驱动程序可能会与MTD层通信,以便将数据导出到存储在SPI闪存(如DataFlash)上的文件系统;而其他协议驱动程序可能会控制音频接口、将触摸屏传感器呈现为输入接口,或者在工业处理期间监视温度和电压级别。而这些可能都共享同一个控制器驱动程序。

struct spi_device”封装了这两种类型的驱动程序之间的控制器端接口。

存在一个最小的SPI编程接口核心,专注于使用驱动程序模型来连接控制器和协议驱动程序,使用主板特定初始化代码提供的设备表。SPI在sysfs中的多个位置显示

/sys/devices/.../CTLR ... physical node for a given SPI controller

/sys/devices/.../CTLR/spiB.C ... spi_device on bus "B",
     chipselect C, accessed through CTLR.

/sys/bus/spi/devices/spiB.C ... symlink to that physical
     .../CTLR/spiB.C device

/sys/devices/.../CTLR/spiB.C/modalias ... identifies the driver
     that should be used with this device (for hotplug/coldplug)

/sys/bus/spi/drivers/D ... driver for one or more spi*.* devices

/sys/class/spi_master/spiB ... symlink to a logical node which could hold
     class related state for the SPI host controller managing bus "B".
     All spiB.* devices share one physical SPI bus segment, with SCLK,
     MOSI, and MISO.

/sys/devices/.../CTLR/slave ... virtual file for (un)registering the
     target device for an SPI target controller.
     Writing the driver name of an SPI target handler to this file
     registers the target device; writing "(null)" unregisters the target
     device.
     Reading from this file shows the name of the target device ("(null)"
     if not registered).

/sys/class/spi_slave/spiB ... symlink to a logical node which could hold
     class related state for the SPI target controller on bus "B".  When
     registered, a single spiB.* device is present here, possible sharing
     the physical SPI bus segment with other SPI target devices.

此时,唯一的类特定状态是总线编号(“spiB”中的“B”),因此这些/sys/class条目仅可用于快速识别总线。

主板特定的初始化代码如何声明SPI设备?

Linux需要多种信息才能正确配置SPI设备。即使对于支持某些自动发现/枚举的芯片,该信息通常也由主板特定的代码提供。

声明控制器

第一种信息是现有SPI控制器的列表。对于基于片上系统(SOC)的主板,这些通常是平台设备,并且控制器可能需要一些platform_data才能正常运行。“struct platform_device”将包括控制器第一个寄存器的物理地址及其IRQ等资源。

平台通常会抽象“注册SPI控制器”操作,可能会将其与初始化引脚配置的代码耦合,以便几个主板的arch/.../mach-*/board-*.c文件都可以共享相同的基本控制器设置代码。这是因为大多数SOC都有多个支持SPI的控制器,通常只有给定主板上实际可用的控制器才应设置和注册。

因此,例如,arch/.../mach-*/board-*.c文件可能具有如下代码

#include <mach/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 pdata __initdata = { ... };

static __init board_init(void)
{
        ...
        /* this board only uses SPI controller #2 */
        mysoc_register_spi(2, &pdata);
        ...
}

SOC特定的实用程序代码可能如下所示

#include <mach/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);

                /* also: 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.
                 */
        }
        ...
}

请注意,即使使用相同的SOC控制器,主板的platform_data也可能不同。例如,在一个主板上,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设备!

非静态配置

当Linux通过SPI包括对MMC/SD/SDIO/DataFlash卡的支持时,这些配置也将是动态的。幸运的是,此类设备都支持基本的设备识别探测,因此它们应该可以正常热插拔。

如何编写“SPI协议驱动程序”?

大多数SPI驱动程序当前都是内核驱动程序,但也支持用户空间驱动程序。在这里,我们只讨论内核驱动程序。

SPI协议驱动程序有点类似于平台设备驱动程序

static struct spi_driver CHIP_driver = {
        .driver = {
                .name           = "CHIP",
                .pm             = &CHIP_pm_ops,
        },

        .probe          = CHIP_probe,
        .remove         = CHIP_remove,
};

驱动程序核心将自动尝试将此驱动程序绑定到任何board_info的modalias为“CHIP”的SPI设备。除非您正在创建管理总线(显示在/sys/class/spi_master下)的设备,否则您的probe()代码可能如下所示。

static int 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()返回时,或者在probe()失败后,驱动程序保证它不会再提交任何此类消息。

  • spi_message是一系列协议操作,作为一个原子序列执行。SPI驱动程序控件包括

    • 双向读取和写入何时开始 ... 通过其spi_transfer请求序列的排列方式;

    • 使用哪些I/O缓冲区 ... 每个spi_transfer包装一个用于每个传输方向的缓冲区,支持全双工(两个指针,在两种情况下可能相同)和半双工(一个指针为NULL)传输;

    • 可选地定义传输后的短延迟 ... 使用spi_transfer.delay.value设置(如果缓冲区长度为零,则此延迟可能是唯一的协议效果) ... 在指定此延迟时,默认的spi_transfer.delay.unit是微秒,但是如果需要,可以将其调整为时钟周期或纳秒;

    • 在传输和任何延迟后,片选是否变为非活动状态 ... 通过使用spi_transfer.cs_change标志;

    • 提示下一个消息是否可能转到同一设备 ... 通过在该原子组中的最后一个传输上使用spi_transfer.cs_change标志,并可能节省芯片取消选择和选择操作的成本。

  • 遵循标准的内核规则,并在您的消息中提供DMA安全的缓冲区。这样,使用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风格的请求,例如写入一个八位命令并读取一个十六位响应 -- spi_w8r16() 是它的一个包装器,完全可以做到这一点。

有些驱动程序可能需要修改spi_device特性,如传输模式、字大小或时钟速率。这可以通过 spi_setup() 完成,它通常在probe()中在对设备进行第一次I/O之前调用。但是,这也可以在没有为该设备挂起任何消息时随时调用。

虽然“spi_device”将是驱动程序的下限,但上限可能包括sysfs(特别是对于传感器读数)、输入层、ALSA、网络、MTD、字符设备框架或其他Linux子系统。

请注意,作为与SPI设备交互的一部分,您的驱动程序必须管理两种类型的内存。

  • I/O缓冲区使用常用的Linux规则,并且必须是DMA安全的。您通常会从堆或空闲页面池中分配它们。不要使用堆栈或任何声明为“static”的内容。

  • spi_message和spi_transfer元数据用于将这些I/O缓冲区粘合到一组协议事务中。这些可以方便地在任何地方分配,包括作为其他一次性分配驱动程序数据结构的一部分。将这些初始化为零。

如果您愿意,可以使用spi_message_alloc()和spi_message_free()便利例程来分配和零初始化具有多个传输的spi_message。

如何编写“SPI控制器驱动程序”?

SPI控制器可能会在platform_bus上注册;编写一个驱动程序来绑定到设备,无论涉及哪个总线。

此类型驱动程序的主要任务是提供“spi_controller”。使用spi_alloc_host()分配主机控制器,并使用spi_controller_get_devdata()获取为该设备分配的驱动程序专用数据。

struct spi_controller   *ctlr;
struct CONTROLLER       *c;

ctlr = spi_alloc_host(dev, sizeof *c);
if (!ctlr)
        return -ENODEV;

c = spi_controller_get_devdata(ctlr);

驱动程序将初始化该spi_controller的字段,包括总线编号(可能与平台设备ID相同)和用于与SPI核心和SPI协议驱动程序交互的三种方法。它还将初始化自己的内部状态。(请参阅下面的总线编号和这些方法。)

初始化spi_controller后,使用 spi_register_controller() 将其发布到系统的其余部分。此时,将提供控制器和任何预先声明的spi设备的设备节点,并且驱动程序模型核心将负责将它们绑定到驱动程序。

如果您需要删除SPI控制器驱动程序,spi_unregister_controller() 将反转 spi_register_controller() 的效果。

总线编号

总线编号很重要,因为这是Linux识别给定SPI总线(共享SCK、MOSI、MISO)的方式。有效的总线编号从零开始。在SOC系统上,总线编号应与芯片制造商定义的编号匹配。例如,硬件控制器SPI2将是总线编号2,并且连接到它的设备的spi_board_info将使用该编号。

如果您没有此类硬件分配的总线编号,并且由于某种原因您无法简单地分配它们,请提供一个负总线编号。然后,它将被动态分配的编号替换。然后,您需要将其视为非静态配置(参见上文)。

SPI主机控制器方法

ctlr->setup(struct spi_device *spi)

这设置了设备时钟速率、SPI模式和字大小。驱动程序可以更改board_info提供的默认值,然后调用spi_setup(spi)来调用此例程。它可能会休眠。

除非每个SPI目标都有自己的配置寄存器,否则不要立即更改它们 ... 否则驱动程序可能会损坏正在为其他SPI设备进行的I/O。

注意

错误警报:由于某种原因,许多spi_controller驱动程序的第一个版本似乎都弄错了。在您编写setup()时,假设控制器正在主动处理另一个设备的传输。

ctlr->cleanup(struct spi_device *spi)

您的控制器驱动程序可以使用spi_device.controller_state来保存其动态与该设备关联的状态。如果您这样做,请务必提供cleanup()方法来释放该状态。

ctlr->prepare_transfer_hardware(struct spi_controller *ctlr)

队列机制将调用此方法来向驱动程序发出信号,指示即将收到消息,因此子系统请求驱动程序通过发出此调用来准备传输硬件。它可能会休眠。

ctlr->unprepare_transfer_hardware(struct spi_controller *ctlr)

队列机制将调用此方法来向驱动程序发出信号,指示队列中不再有任何挂起的消息,并且它可以放松硬件(例如,通过电源管理调用)。它可能会休眠。

ctlr->transfer_one_message(struct spi_controller *ctlr, struct spi_message *mesg)

子系统调用驱动程序来传输单个消息,同时对同时到达的传输进行排队。当驱动程序完成此消息时,它必须调用 spi_finalize_current_message(),以便子系统可以发出下一个消息。它可能会休眠。

ctrl->transfer_one(struct spi_controller *ctlr, struct spi_device *spi, struct spi_transfer *transfer)

子系统调用驱动程序来传输单个传输,同时对同时到达的传输进行排队。当驱动程序完成此传输时,它必须调用 spi_finalize_current_transfer(),以便子系统可以发出下一个传输。它可能会休眠。注意:transfer_one和transfer_one_message是互斥的;当两者都设置时,通用子系统不会调用您的transfer_one回调。

返回值

  • negative errno:错误

  • 0:传输已完成

  • 1:传输仍在进行中

ctrl->set_cs_timing(struct spi_device *spi, u8 setup_clk_cycles, u8 hold_clk_cycles, u8 inactive_clk_cycles)

此方法允许SPI客户端驱动程序请求SPI主机控制器来配置设备特定的CS建立、保持和非活动时序要求。

已弃用的方法

ctrl->transfer(struct spi_device *spi, struct spi_message *message)

这不得休眠。它的责任是安排发生传输并发出其complete()回调。这两者通常会在稍后发生,在其他传输完成后,如果控制器空闲,则需要重新启动它。此方法未在排队的控制器上使用,如果实现了transfer_one_message()和(un)prepare_transfer_hardware(),则必须为NULL。

SPI消息队列

如果您对SPI子系统提供的标准排队机制感到满意,只需实现上面指定的排队方法。使用消息队列的优点是集中了大量代码并提供了方法的纯进程上下文执行。消息队列也可以在高优先级SPI流量上提升到实时优先级。

除非选择了SPI子系统中的排队机制,否则驱动程序的大部分将管理现在已弃用的函数transfer()提供的I/O队列。

该队列可能纯粹是概念性的。例如,仅用于低频传感器访问的驱动程序可能可以很好地使用同步PIO。

但是队列可能非常真实,使用message->queue、PIO、通常是DMA(特别是如果根文件系统位于SPI闪存中),以及执行上下文(如IRQ处理程序、tasklet或工作队列(如keventd))。您的驱动程序可以根据需要进行花哨或简单操作。这样的transfer()方法通常只是将消息添加到队列,然后启动一些异步传输引擎(除非它已经在运行)。

SPI 协议的扩展

由于 SPI 没有正式的规范或标准,芯片制造商可以以略有不同的方式实现 SPI 协议。在大多数情况下,来自不同供应商的 SPI 协议实现是彼此兼容的。例如,在 SPI 模式 0 (CPOL=0, CPHA=0) 中,总线线路的行为可能如下所示

nCSx ___                                                                   ___
        \_________________________________________________________________/
        •                                                                 •
        •                                                                 •
SCLK         ___     ___     ___     ___     ___     ___     ___     ___
     _______/   \___/   \___/   \___/   \___/   \___/   \___/   \___/   \_____
        •   :   ;   :   ;   :   ;   :   ;   :   ;   :   ;   :   ;   :   ; •
        •   :   ;   :   ;   :   ;   :   ;   :   ;   :   ;   :   ;   :   ; •
MOSI XXX__________         _______                 _______         ________XXX
0xA5 XXX__/ 1     \_0_____/ 1     \_0_______0_____/ 1     \_0_____/ 1    \_XXX
        •       ;       ;       ;       ;       ;       ;       ;       ; •
        •       ;       ;       ;       ;       ;       ;       ;       ; •
MISO XXX__________         _______________________          _______        XXX
0xBA XXX__/     1 \_____0_/     1       1       1 \_____0__/    1  \____0__XXX

图例

• marks the start/end of transmission;
: marks when data is clocked into the peripheral;
; marks when data is clocked into the controller;
X marks when line states are not specified.

在少数情况下,芯片通过指定其他 SPI 协议不具备的线路行为(例如,CS 未置位时的数据线路状态)来扩展 SPI 协议。这些不同的 SPI 协议、模式和配置由不同的 SPI 模式标志支持。

MOSI 空闲状态配置

常见的 SPI 协议实现没有为控制器不时钟输出数据时 MOSI 线路的任何状态或行为指定。但是,确实存在外设要求在未时钟输出数据时 MOSI 线路处于特定状态。例如,如果外设期望控制器不时钟输出数据时 MOSI 线路为高电平 (SPI_MOSI_IDLE_HIGH),那么 SPI 模式 0 中的传输将如下所示

nCSx ___                                                                   ___
        \_________________________________________________________________/
        •                                                                 •
        •                                                                 •
SCLK         ___     ___     ___     ___     ___     ___     ___     ___
     _______/   \___/   \___/   \___/   \___/   \___/   \___/   \___/   \_____
        •   :   ;   :   ;   :   ;   :   ;   :   ;   :   ;   :   ;   :   ; •
        •   :   ;   :   ;   :   ;   :   ;   :   ;   :   ;   :   ;   :   ; •
MOSI _____         _______         _______         _______________         ___
0x56      \_0_____/ 1     \_0_____/ 1     \_0_____/ 1       1     \_0_____/
        •       ;       ;       ;       ;       ;       ;       ;       ; •
        •       ;       ;       ;       ;       ;       ;       ;       ; •
MISO XXX__________         _______________________          _______        XXX
0xBA XXX__/     1 \_____0_/     1       1       1 \_____0__/    1  \____0__XXX

图例

• marks the start/end of transmission;
: marks when data is clocked into the peripheral;
; marks when data is clocked into the controller;
X marks when line states are not specified.

在这种对常用 SPI 协议的扩展中,MOSI 线路状态被指定为:当 CS 被置位但控制器未向外设时钟输出数据时,以及当 CS 未被置位时,都保持高电平。

需要此扩展的外设必须通过将 SPI_MOSI_IDLE_HIGH 位设置到它们的 struct spi_device 的 mode 属性中,并调用 spi_setup() 来请求它。 支持此扩展的控制器应通过在它们的 struct spi_controller 的 mode_bits 属性中设置 SPI_MOSI_IDLE_HIGH 来指示它。 将 MOSI 配置为空闲低电平是类似的,但使用 SPI_MOSI_IDLE_LOW 模式位。

感谢

Linux-SPI 讨论的贡献者包括(按姓氏字母顺序排列):

  • Mark Brown

  • David Brownell

  • Russell King

  • Grant Likely

  • Dmitry Pervushin

  • Stephen Street

  • Mark Underwood

  • Andrew Victor

  • Linus Walleij

  • Vitaly Wool