SPI 用户空间 API¶
SPI 设备具有有限的用户空间 API,支持对 SPI 从设备进行基本的半双工 read() 和 write() 访问。 通过使用 ioctl() 请求,还可以进行全双工传输和设备 I/O 配置。
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <linux/types.h>
#include <linux/spi/spidev.h>
您可能想要使用此编程接口的一些原因是:
在不易崩溃的环境中进行原型设计;用户空间中的野指针通常不会导致任何 Linux 系统崩溃。
开发用于与充当 SPI 从设备的微控制器通信的简单协议,您可能需要经常更改这些协议。
当然,有些驱动程序永远不能在用户空间中编写,因为它们需要访问用户空间无法访问的内核接口(例如 IRQ 处理程序或驱动程序堆栈的其他层)。
设备创建,驱动程序绑定¶
spidev 驱动程序包含支持不同硬件拓扑表示的 SPI 设备列表。
以下是 spidev 驱动程序支持的 SPI 设备表
struct spi_device_id spidev_spi_ids[]:当使用
struct spi_board_info
定义具有与表中条目之一匹配的 .modalias 字段时,可以绑定的设备列表。struct of_device_id spidev_dt_ids[]:当使用具有与表中条目之一匹配的兼容字符串的设备树节点定义这些设备时,可以绑定的设备列表。
struct acpi_device_id spidev_acpi_ids[]:当使用 _HID 与表中条目之一匹配的 ACPI 设备对象定义这些设备时,可以绑定的设备列表。
如果相关表中尚未包含您的 SPI 设备名称,建议您为您的 SPI 设备名称添加到相关表中。 为此,请将 spidev 的补丁发布到 linux-spi@vger.kernel.org 邮件列表。
过去支持使用“spidev”名称定义 SPI 设备。 例如,作为 .modalias = “spidev” 或 compatible = “spidev”。 但 Linux 内核不再支持这种方法,而必须使用表中列出的真实 SPI 设备名称。
没有真实的 SPI 设备名称会导致打印错误并导致 spidev 驱动程序探测失败。
Sysfs 还支持用户空间驱动的驱动程序绑定/取消绑定到未使用上述表之一自动绑定的设备。 要使 spidev 驱动程序绑定到此类设备,请使用以下命令:
echo spidev > /sys/bus/spi/devices/spiB.C/driver_override
echo spiB.C > /sys/bus/spi/drivers/spidev/bind
当 spidev 驱动程序绑定到 SPI 设备时,该设备的 sysfs 节点将包括一个带有“dev”属性的子设备节点,udev 或 mdev(来自 BusyBox 的 udev 替代品;它的功能较少,但通常足够)可以理解该属性。
对于具有总线 B 上片选 C 的 SPI 设备,您应该会看到:
- /dev/spidevB.C ...
字符特殊设备,主设备号为 153,次设备号为动态选择。 这是用户空间程序将打开的节点,由“udev”或“mdev”创建。
- /sys/devices/.../spiB.C ...
照常,SPI 设备节点将是其 SPI 主控制器的子节点。
- /sys/class/spidev/spidevB.C ...
当“spidev”驱动程序绑定到该设备时创建。(目录或符号链接,具体取决于是否启用了“已弃用的 sysfs 文件”Kconfig 选项。)
不要尝试手动管理 /dev 字符设备特殊文件节点。 这很容易出错,您需要仔细注意系统安全问题;udev/mdev 应该已经配置好安全措施。
如果您从该设备取消绑定“spidev”驱动程序,则这两个“spidev”节点(在 sysfs 和 /dev 中)应自动删除(分别由内核和 udev/mdev 删除)。 您可以通过删除“spidev”驱动程序模块来取消绑定,这将影响所有使用此驱动程序的设备。 您还可以通过让内核代码删除 SPI 设备来取消绑定,可能是通过删除其 SPI 控制器的驱动程序(因此其 spi_master 消失)。
由于这是一个标准的 Linux 设备驱动程序——即使它只是碰巧向用户空间公开了一个低级 API——它可以一次与任意数量的设备关联。 只需为每个此类 SPI 设备提供一个 spi_board_info 记录,您将为每个设备获得一个 /dev 设备节点。
基本字符设备 API¶
对 /dev/spidevB.D 文件执行正常的 open() 和 close() 操作会如您所愿。
标准的 read() 和 write() 操作显然只是半双工,并且片选在这些操作之间被禁用。 使用 SPI_IOC_MESSAGE(N) 请求,可以进行全双工访问和不禁用片选的组合操作。
多个 ioctl() 请求使您的驱动程序可以读取或覆盖设备的当前数据传输参数设置:
- SPI_IOC_RD_MODE, SPI_IOC_WR_MODE ...
传递一个指向字节的指针,该字节将返回 (RD) 或分配 (WR) SPI 传输模式。 使用常量 SPI_MODE_0..SPI_MODE_3; 或者,如果您喜欢,可以组合 SPI_CPOL(时钟极性,如果设置则空闲高)或 SPI_CPHA(时钟相位,如果设置则在下降沿采样)标志。 请注意,此请求仅限于适合单个字节的 SPI 模式标志。
- SPI_IOC_RD_MODE32, SPI_IOC_WR_MODE32 ...
传递一个指向 uin32_t 的指针,该指针将返回 (RD) 或分配 (WR) 完整的 SPI 传输模式,不限于适合一个字节的位。
- SPI_IOC_RD_LSB_FIRST, SPI_IOC_WR_LSB_FIRST ...
传递一个指向字节的指针,该字节将返回 (RD) 或分配 (WR) 用于传输 SPI 字的位对齐方式。 零表示 MSB 优先;其他值表示不太常见的 LSB 优先编码。 在这两种情况下,指定的值在每个字中都是右对齐的,因此未使用的 (TX) 或未定义的 (RX) 位位于 MSB 中。
- SPI_IOC_RD_BITS_PER_WORD, SPI_IOC_WR_BITS_PER_WORD ...
传递一个指向字节的指针,该字节将返回 (RD) 或分配 (WR) 每个 SPI 传输字中的位数。 值零表示八位。
- SPI_IOC_RD_MAX_SPEED_HZ, SPI_IOC_WR_MAX_SPEED_HZ ...
传递一个指向 u32 的指针,该指针将返回 (RD) 或分配 (WR) 最大 SPI 传输速度,以 Hz 为单位。 控制器不一定能分配该特定的时钟速度。
注意
目前没有异步 I/O 支持; 一切都是纯粹同步的。
目前无法报告用于将数据移入/移出给定设备的实际比特率。
从用户空间,您目前无法更改片选极性; 这可能会损坏与其他共享 SPI 总线的设备的传输。 每个 SPI 设备在不活动时都会被取消选择,从而允许其他驱动程序与其他设备通信。
每个 I/O 请求可以传输到 SPI 设备的字节数有限制。 它默认为一个页面,但可以使用模块参数进行更改。
由于 SPI 没有低级传输确认,因此在与不存在的设备通信时,您通常看不到任何 I/O 错误。
全双工字符设备 API¶
有关如何使用全双工编程接口的示例,请参阅 spidev_fdx.c 示例程序。(尽管它不执行全双工传输。)该模型与内核 spi_sync()
请求中使用的模型相同;各个传输提供与内核驱动程序相同的能力(除了它不是异步的)。
该示例显示了一个半双工 RPC 样式的请求和响应消息。 这些请求通常要求芯片在请求和响应之间不被取消选择。 可以将多个此类请求链接到一个内核请求中,甚至允许在每个响应后取消选择芯片。(其他协议选项包括更改每个传输段的字大小和比特率。)
要发出全双工请求,请为同一传输提供 rx_buf 和 tx_buf。 如果它们是同一个缓冲区,则甚至可以。