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 设备。你的 probe() 代码可能看起来像这样,除非你正在创建一个管理总线的设备(出现在 /sys/class/spi_master 下)。

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()。异步请求可以在任何上下文(中断处理程序、任务等)中发出,并使用消息提供的回调报告完成情况。在检测到任何错误后,芯片将被取消选择,并且该 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” 的内容。

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

如果需要,可以使用 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 回调。

返回值

  • 负数的 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 协议没有的线路行为来扩展 SPI 协议(例如,CS 未置位时的数据线状态)。不同的 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