Linux内核学习17--SPI子系统
1 Linux下SPI子系统简介关于SPI协议细节之前写过总线学习3--SPI_spi a0-CSDN博客这次就不多写了这次主要关注Linux下的使用使用的环境是树莓派5。还是先看图吧毕竟无图无真相。这个图B格看起来有点高。不过对于BSP这边的开发我感觉下面这个图明了一些。实用性强得多。来自https://www.eet-china.com/mp/a131804.html一共分为用户层 → SPI 核心框架 → 控制器驱动 → 硬件寄存器。用户空间层spidev.c用户层就是spidev.c提供。说白点就是给上层提供了ioctl接口这样可以在应用层使用节点或者编程来控制。/dev/spidevX.Y 字符设备XSPI 主机号Y 片选号提供标准 ioctl 接口应用可直接收发 SPI 数据无需写内核驱动工具spi-config、spi-pipe、spidev_test 测试程序。SPI 核心层spi.c/spi.h在这一层就是内核接口层说白点就是提供了SPI 内核标准接口实现文件spi_write()、spi_read()、spi_sync()。驱动程序接口层。同时在这里内核中间调度层定义通用数据结构、传输队列、消息处理、总线注册 / 注销、锁机制是上下层桥梁不绑定任何硬件。SPI 控制器驱动master 驱动对接 SoC 内置 SPI 控制器如 STM32 SPI、高通 QCOM SPI、全志 SPI、树莓派 BCM2835 SPI实现底层时序、时钟、片选、DMA、中断收发。SPI 外设设备驱动slave 驱动针对外接芯片Flash (W25Q)、ADC、显示屏、传感器、射频模块等调用核心层接口发起传输。最后的两层其实基本不用怎么关心。2 内核节点2.1 原始状态在树莓派 5RPi5BCM2712SPI0 默认关闭不会自动生成 spidev 节点。此时状态如下tomraspberrypi:~$ ls /dev/spide* /dev/spidev10.0 tomraspberrypi:~$ lsmod | grep spi spidev 49152 0 spi_bcm2835 49152 0此时虽然有一个spidev10.0。但是并不是40pin接口的SPI。树莓派 5BCM2712有两套 SPI 硬件控制器内核驱动传统 spi-bcm2835对应老 BCM2711/2837 架构的 SPI0、SPI140 针排针那两组新 DWDesignWareSPI 控制器 spi_dwRP1 辅助芯片自带多路 SPI内核枚举时编号排到了 10所以生成 /dev/spidev10.0设备节点控制器驱动物理引脚位置用途spidev0.0 / spidev0.1spi_bcm283540 针排针GPIO9/10/11/7/8Pin19/21/23/26/24外接 OLED、W25Q、ADC、射频模块最常用spidev10.0spi_dwRP1RP1 芯片内部引出不在 40 针 GPIO板载内部外设、HAT 扩展底板专用普通外设不接这里驱动解释如下spidev 49152 0 # 通用用户透传驱动spi_bcm2835 49152 0 # 40针GPIO标准SPI0/SPI1主机驱动spi_dw_mmio spi_dw # RP1扩展SPIspi10总线来源2.2 SPIDEV状态要使用spidev必须手动开启开启dtparamspion后这里默认DTS子节点compatiblespidev内核会自动加载 spidev 驱动直接生成/dev/spidev0.0、/dev/spidev0.1这时效果如下tomraspberrypi:~$ ls /dev/spide* /dev/spidev0.0 /dev/spidev0.1 /dev/spidev10.0 tomraspberrypi:~$ lsmod | grep spi spidev 49152 0 spi_dw_mmio 49152 0 spi_bcm2835 49152 0 spi_dw 49152 1 spi_dw_mmio此时在设备树下生成两个节点/sys/firmware/devicetree/base/axi/pcie1000120000/rp1/spi50000/spidev0 /sys/firmware/devicetree/base/axi/pcie1000120000/rp1/spi50000/spidev1为什么是两个因为在原始的spi0中定义了两路片选。rp1_spi0: spi50000 { reg 0xc0 0x40050000 0x0 0x130; compatible snps,dw-apb-ssi; interrupts RP1_INT_SPI0 IRQ_TYPE_LEVEL_HIGH; clocks rp1_clocks RP1_CLK_SYS; clock-names ssi_clk; #address-cells 1; #size-cells 0; num-cs 2; dmas rp1_dma RP1_DMA_SPI0_TX, rp1_dma RP1_DMA_SPI0_RX; dma-names tx, rx; status disabled;此时可以看到这些节点tomraspberrypi:/sys/bus/spi/devices$ ls spi0.0 spi0.1 spi10.0这个部分是由spi.c创建不是spidev驱动本身。所以操作的方法比较通用所有的驱动都可以这么搞。节点下面大概内容有这些tomraspberrypi:/sys/bus/spi/devices/spi0.0$ ls -l total 0 lrwxrwxrwx 1 root root 0 Jun 12 10:00 driver - ../../../../../../../../bus/spi/drivers/spidev -rw-r--r-- 1 root root 16384 Jun 12 10:42 driver_override -r--r--r-- 1 root root 16384 Jun 12 10:42 modalias lrwxrwxrwx 1 root root 0 Jun 12 10:42 of_node - ../../../../../../../../firmware/devicetree/base/axi/pcie1000120000/rp1/spi50000/spidev0 drwxr-xr-x 2 root root 0 Jun 12 10:42 power drwxr-xr-x 3 root root 0 Jun 12 10:00 spidev drwxr-xr-x 2 root root 0 Jun 12 10:42 statistics lrwxrwxrwx 1 root root 0 Jun 12 10:00 subsystem - ../../../../../../../../bus/spi -rw-r--r-- 1 root root 16384 Jun 12 10:00 uevent作用如下目录/文件名称类型核心作用内核层通俗解释应用层spidev目录绑定了 Linux 通用 SPI 驱动的标志只要它在就能在/dev/下找到spidev0.0从而能用代码直接读写 SPI 硬件。driver符号链接指向当前正在控制该设备的内核驱动程序告诉现在是谁在管这个设备。如果指向spidev说明是通用驱动如果指向特定芯片驱动如某个屏幕驱动说明被专属驱动占用了。driver_override文件允许手动强制指定该设备的驱动程序戏称“强扭的瓜”。可以往里写特定驱动的名字强行让系统用指定的驱动去管这个设备。modalias文件显示设备的内核模块别名设备的“身份证号”内核靠它来自动匹配并加载正确的驱动程序。of_node符号链接指向树莓派设备树Device Tree中的对应节点连向硬件的“出厂配置单”能看到硬件引脚分配、最大波特率等底层配置。statistics目录存放该 SPI 设备的通信统计数据相当于“计费账单”里面记录了发了多少字节、报错了多少次调优和排错时很有用。power目录硬件电源管理接口控制设备的省电、休眠和唤醒模式。subsystem符号链接指向设备所属的内核子系统分类认祖归宗这里指向sys/bus/spi表明它属于 SPI 总线家族。uevent文件内核与用户空间热插拔管理器的通信接口“大喇叭”当硬件启动或移除时内核通过它通知系统如udev去自动创建或删除/dev/下的设备文件。此时可以手动灌数据。while true; do echo -ne \xff\xff\xff\xff /dev/spidev0.0; sleep 0.01; done2.3 自定义SPI驱动一旦用overlay绑定其他外设驱动w25q、mmc_spi、自研屏驱对应 CS 通道的spidev就会消失二者互斥。要记得使用官方接口注册module_spi_driver(ecx335c_driver);可以使用的接口大概是函数名称功能描述核心参数说明spi_write()纯发送数据。向 SPI 设备写入一段指定长度的数据缓冲区。(spi, buf, len)•buf: 发送数据指针•len: 字节数spi_read()纯接收数据。从 SPI 设备读取一段指定长度的数据到缓冲区。(spi, buf, len)•buf: 接收缓存指针spi_write_then_read()先发后收半双工。常用于“发送寄存器地址 - 读取寄存器值”的场景。中间片选不拉高。(spi, txbuf, txlen, rxbuf, rxlen)•txbuf/txlen: 发送缓存与长度•rxbuf/rxlen: 接收缓存与长度spi_w8r8()spi_write_then_read的精简版。发送 8 位数据紧接着读取 8 位数据。返回值即为读取到的 8 位数据或负数错误码。(spi, cmd)•cmd: 要发送的 8 位指令/地址spi_w8r16()发送 8 位数据紧接着读取 16 位数据。内部会自动处理大端/小端字节序的转换。(spi, cmd)• 返回值: 读取到的 16 位数据还有很多就不列举了。。。2.4 片选在 Linux 的 SPI 命名规范中spiX.Y的格式是这样定义的X代表SPI 控制器的编号总线号。spi0意味着它们共享树莓派 5 同一个 SPI0 硬件总线共享时钟线 CLK、主出从入线 MOSI、主入从出线 MISO。Y代表片选引脚的编号Chip Select。spi0.1占用的是 SPI0 的片选 1通常对应树莓派引脚上的SPI0_CE1。spi0.2占用的是 SPI0 的片选 2通常对应树莓派引脚上的SPI0_CE2。此时可以加载两个设备两个驱动。3 调试方法3.1 spidev_test 环回测试在外设驱动没调通前先把设备树里的兼容性临时改成标准的虚拟节点compatible rohm,dh2228fv; /* 或者直接是 generic 的 linux,spidev */重启后系统会在/dev/下吐出一个/dev/spidev0.0的裸设备节点。拿一根跳线或者镊子把树莓派或板子引脚上的 SPI_MOSI 和 SPI_MISO 短接短路在一起。运行 Linux 内核自带的测试工具内核源码tools/spi/spidev_test.c编译出来的二进制./spidev_test -D /dev/spidev0.0 -v -p Hello Tom!如果终端里成功打印出发送和接收完全一致的Hello Tom!说明SoC、内核核心层、控制器驱动、DMA、引脚复用全部 100% 完好。皮球直接踢给硬件工程师去查外设芯片断线或供电问题或应用驱动去查初始化序列。如果读出来全是00说明总线自己都没通。3.2 ftrace 传输追踪cd /sys/kernel/tracing/ echo 0 tracing_on echo trace # 追踪标准 Linux SPI 核心层的两大骨架事件 echo spi:* set_event # 开启内核 SPI 核心层自带的 tracepoint极其精准 echo 1 tracing_on # 执行你的读写测试 echo 0 tracing_on # 查看账本 cat trace | head -n 30# 1. 切换到 root sudo su # 2. 擦亮内核的追踪器镜片 echo 0 /sys/kernel/tracing/tracing_on echo /sys/kernel/tracing/trace # 3. 开启 SPI 模块搬运数据的事件监听抓取发送和接收 echo 1 /sys/kernel/tracing/events/spi/spi_transfer_start/enable # 4. 打开总开关 echo 1 /sys/kernel/tracing/tracing_on # 5. 此时去运行你的调屏测试程序或者往 /dev/spidev10.0 灌数据 # 6. 见证奇迹的时刻查看内核抓到的 SPI 裸波形流 cat /sys/kernel/tracing/trace | tail -n 503.3 动态打印控制Dynamic DebugLinux 内核有一个神级机制叫dynamic_debug。你不需要重新编译高通内核就能在运行时一键开启SPI 核心层的所有隐藏pr_debug级日志# 1. 挂载 debugfs安卓通常默认挂载了 mount -t debugfs none /sys/kernel/debug/ # 2. 一键命令内核把所有属于 spi 核心框架和高通 geni-spi 驱动里的调试日志全部激活 echo file drivers/spi/* p /sys/kernel/debug/dynamic_debug/control echo file drivers/platform/msm/gpi/* p /sys/kernel/debug/dynamic_debug/control # 高通DMA传输日志 # 3. 此时去看 dmesg你会看到每一次 SPI 传输的长度、片选切换、甚至中断状态都被吐了出来 dmesg -w3.4 查看SPI状态查看当前被哪个驱动占用tomraspberrypi:~$ ls -l /sys/bus/spi/devices/spi0.0/driver lrwxrwxrwx 1 root root 0 Jun 13 09:41 /sys/bus/spi/devices/spi0.0/driver - ../../../../../../../../bus/spi/drivers/spidev查看Pin脚复用tomraspberrypi:/sys/bus/spi/devices$ cat /sys/kernel/debug/pinctrl/1f000d0000.gpio-pinctrl-rp1/pinmux-pins Pinmux settings per pin Format: pin (name): mux_owner gpio_owner hog? pin 0 (gpio0): (MUX UNCLAIMED) (GPIO UNCLAIMED) pin 1 (gpio1): (MUX UNCLAIMED) (GPIO UNCLAIMED) pin 2 (gpio2): (MUX UNCLAIMED) (GPIO UNCLAIMED) pin 3 (gpio3): (MUX UNCLAIMED) (GPIO UNCLAIMED) pin 4 (gpio4): (MUX UNCLAIMED) (GPIO UNCLAIMED) pin 5 (gpio5): (MUX UNCLAIMED) (GPIO UNCLAIMED) pin 6 (gpio6): (MUX UNCLAIMED) (GPIO UNCLAIMED) pin 7 (gpio7): 1f00050000.spi pinctrl-rp1:578 function spi0 group gpio7 pin 8 (gpio8): 1f00050000.spi pinctrl-rp1:579 function spi0 group gpio8 pin 9 (gpio9): 1f00050000.spi (GPIO UNCLAIMED) function spi0 group gpio9 pin 10 (gpio10): 1f00050000.spi (GPIO UNCLAIMED) function spi0 group gpio10 pin 11 (gpio11): 1f00050000.spi (GPIO UNCLAIMED) function spi0 group gpio11 pin 12 (gpio12): (MUX UNCLAIMED) (GPIO UNCLAIMED) pin 13 (gpio13): (MUX UNCLAIMED) (GPIO UNCLAIMED) pin 14 (gpio14): (MUX UNCLAIMED) (GPIO UNCLAIMED) pin 15 (gpio15): (MUX UNCLAIMED) (GPIO UNCLAIMED) pin 16 (gpio16): (MUX UNCLAIMED) (GPIO UNCLAIMED) pin 17 (gpio17): (MUX UNCLAIMED) (GPIO UNCLAIMED) pin 18 (gpio18): (MUX UNCLAIMED) (GPIO UNCLAIMED) pin 19 (gpio19): (MUX UNCLAIMED) (GPIO UNCLAIMED) pin 20 (gpio20): (MUX UNCLAIMED) (GPIO UNCLAIMED) pin 21 (gpio21): (MUX UNCLAIMED) (GPIO UNCLAIMED) pin 22 (gpio22): (MUX UNCLAIMED) (GPIO UNCLAIMED) pin 23 (gpio23): (MUX UNCLAIMED) (GPIO UNCLAIMED) pin 24 (gpio24): (MUX UNCLAIMED) (GPIO UNCLAIMED) pin 25 (gpio25): (MUX UNCLAIMED) (GPIO UNCLAIMED) pin 26 (gpio26): (MUX UNCLAIMED) (GPIO UNCLAIMED) pin 27 (gpio27): (MUX UNCLAIMED) (GPIO UNCLAIMED) pin 28 (gpio28): (MUX UNCLAIMED) (GPIO UNCLAIMED) pin 29 (gpio29): (MUX UNCLAIMED) (GPIO UNCLAIMED) pin 30 (gpio30): (MUX UNCLAIMED) (GPIO UNCLAIMED) pin 31 (gpio31): (MUX UNCLAIMED) (GPIO UNCLAIMED) pin 32 (gpio32): (MUX UNCLAIMED) pinctrl-rp1:603 pin 33 (gpio33): (MUX UNCLAIMED) (GPIO UNCLAIMED) pin 34 (gpio34): (MUX UNCLAIMED) pinctrl-rp1:605 pin 35 (gpio35): (MUX UNCLAIMED) (GPIO UNCLAIMED) pin 36 (gpio36): (MUX UNCLAIMED) (GPIO UNCLAIMED) pin 37 (gpio37): (MUX UNCLAIMED) (GPIO UNCLAIMED) pin 38 (gpio38): (MUX UNCLAIMED) (GPIO UNCLAIMED) pin 39 (gpio39): (MUX UNCLAIMED) (GPIO UNCLAIMED) pin 40 (gpio40): (MUX UNCLAIMED) (GPIO UNCLAIMED) pin 41 (gpio41): (MUX UNCLAIMED) (GPIO UNCLAIMED) pin 42 (gpio42): 1f00200000.usb (GPIO UNCLAIMED) function vbus1 group gpio42 pin 43 (gpio43): 1f00200000.usb (GPIO UNCLAIMED) function vbus1 group gpio43 pin 44 (gpio44): (MUX UNCLAIMED) pinctrl-rp1:615 pin 45 (gpio45): 1f0009c000.pwm (GPIO UNCLAIMED) function pwm1 group gpio45 pin 46 (gpio46): (MUX UNCLAIMED) pinctrl-rp1:617 pin 47 (gpio47): (MUX UNCLAIMED) (GPIO UNCLAIMED) pin 48 (gpio48): (MUX UNCLAIMED) (GPIO UNCLAIMED) pin 49 (gpio49): (MUX UNCLAIMED) (GPIO UNCLAIMED) pin 50 (gpio50): (MUX UNCLAIMED) (GPIO UNCLAIMED) pin 51 (gpio51): (MUX UNCLAIMED) (GPIO UNCLAIMED) pin 52 (gpio52): (MUX UNCLAIMED) (GPIO UNCLAIMED) pin 53 (gpio53): (MUX UNCLAIMED) (GPIO UNCLAIMED)此时可以看到gpio7gpio8gpio9gpio10gpio11已经配置成了SPI。3.5 动态换驱动如果直接换会报错tomraspberrypi:~/dttest$ sudo dtoverlay ecx335c-overlay.dtbo [sudo] password for tom: * Failed to apply overlay 0_ecx335c-overlay (kernel)查肯内核报错[ 2637.268644] OF: overlay: WARNING: memory leak will occur if overlay removed, property: /axi/pcie1000120000/rp1/spi50000/status [ 2637.268735] spidev spi0.0: chipselect 0 already in use [ 2637.268738] spi_master spi0: spi_device register error /axi/pcie1000120000/rp1/spi50000/my_oled_panel0 [ 2637.268744] of_spi_notify: failed to create for /axi/pcie1000120000/rp1/spi50000/my_oled_panel0 [ 2637.268748] OF: changeset notifier error /axi/pcie1000120000/rp1/spi50000/my_oled_panel0 [ 2637.268753] OF: overlay: overlay apply changeset entry notify error -16核心操作bind /unbind 切换驱动最常用调试驱动目录统一路径/sys/bus/spi/drivers/[驱动名]/可用驱动列表查看ls /sys/bus/spi/drivers/spidev、w25qxx 等解绑当前驱动触发驱动 remove 函数把设备名写入对应驱动的 unbind 文件# 解绑spidev echo spi0.0 | sudo tee /sys/bus/spi/drivers/spidev/unbind # 解绑w25q闪存驱动 echo spi0.0 | sudo tee /sys/bus/spi/drivers/w25qxx/unbind4 高通平台查了一下基本也没啥特别的还是用标准的接口弄吧。可能会涉及到检查时钟Clock状态通过 debugfs 查看该 QUP 分组的 SPI 核心时钟和外设时钟如 gcc_qup_spi_clk是否真正使能以及频率是否正确。cat /sys/kernel/debug/clk/clk_summary | grep -i qup