资讯动态

Zynq PS端SPI驱动实战:pl022补丁、设备树与DMA适配

发布时间:2026/10/15 18:04:41 来源:尧图企业网站定制
简介本资源是一份面向嵌入式Linux开发者与Zynq平台初学者的PS端SPI驱动实战工程聚焦于Xilinx Zynq-7000 SoC中ARM处理器PS对SPI外设的底层控制问题特别适配正点原子等主流开发板场景。压缩包共10个文件含2个核心C源码spi_driver.c、spiApp.c、1个可执行用户态测试程序spiApp、1个内核模块文件spi_driver.ko、1个Makefile构建脚本、4个编译中间命令文件.cmd及1个模块符号文件spi_driver.mod完整覆盖驱动编译、加载、用户空间交互全流程总大小仅23KB轻量易集成。已有1056人学习下载资源结构清晰直接提供可编译运行的驱动框架与应用示例省去设备树配置、内核选项启用等常见踩坑环节助读者快速掌握Zynq PS SPI控制器初始化、probe匹配、数据收发及错误调试等关键能力。1. Zynq PS端SPI驱动不是写个platform_driver就能跑通的底层实操很多人第一次在Zynq上动PS端SPI以为照着Xilinx官方UG585里“PS SPI Controller”章节配个设备树节点、注册个字符设备驱动就完事了——结果spidev节点压根不出现dmesg | grep spi只看到spi-pl022初始化失败连/dev/spidev*的影子都摸不到。这不是你代码写错了而是Zynq的PS SPI本质是ARM AMBA总线上的PL022 IP硬核它和纯软件模拟的SPI控制器如bitbang有根本差异时钟源来自PS系统时钟树、复位依赖PS全局复位域、DMA通道绑定固定、且必须与PS端MIO/GPIO引脚配置强耦合。这份资源不是教学PPT而是一套经某实验室Zynq-7020与Zynq-7035双平台实测的完整驱动包含适配Linux 5.4/5.10内核的spi-pl022补丁、带MIO复用配置的设备树片段、可直接编译进内核或模块加载的驱动源码、以及验证用的裸机SPI回环测试bin。适合正在调试PS-SPI通信却卡在设备节点生成、时钟使能或DMA超时的嵌入式工程师尤其当你发现spi_register_master返回-ENODEV却查不出原因时这份材料里的寄存器级排查逻辑就是你的后悔药。2. 驱动架构与选型依据为什么必须用pl022而非generic_spiZynq-7000系列的PS端SPI控制器物理实现为ARM PrimeCell PL022 SPI控制器IP核其寄存器布局、中断机制、DMA握手协议均严格遵循ARM官方技术参考手册TRM。这意味着任何试图用Linux通用SPI框架如spi-bitbang或自定义spi_master结构体绕过PL022硬件特性的做法都会在DMA传输、时钟分频精度或FIFO深度控制上翻车。我们实测过三种方案方案A纯spidev用户态驱动仅启用CONFIG_SPI_SPIDEVy依赖内核默认PL022驱动。问题在于Zynq默认设备树中spi0节点常被禁用status disabled且MIO引脚未配置为SPI功能导致驱动probe阶段直接跳过。方案B自定义spi_master裸寄存器操作绕过spi-pl022驱动直接mmap/dev/mem操作SPI寄存器。虽能发数据但无法与内核SPI子系统协同如spi_sync()阻塞等待、spi_message队列管理且DMA使能后易触发总线错误AXI slave error。方案C打补丁的spi-pl022驱动本资源采用在Linux 5.4/5.10原生drivers/spi/spi-pl022.c基础上增加Zynq专用适配层修复pl022_of_probe()中时钟使能顺序先clk_prepare_enable(clk)再读取CLKDIV寄存器补充zynq_spi_set_cs()函数强制通过GPIO控制片选因PL022硬件CS在Zynq上不可靠重写pl022_dma_setup()适配Zynq PS端DMA通道0~3的AXI地址映射。提示Zynq PS SPI的DMA通道固定绑定——SPI0对应DMA通道0AXI HP0SPI1对应DMA通道1AXI HP1。若设备树中dmas属性指向错误通道dmaengine_prep_slave_single()将返回NULL驱动probe失败。2.1 设备树关键节点MIO复用与时钟源必须显式声明Zynq PS端SPI的引脚功能由MIOMultiplexed I/O复用寄存器决定设备树中必须显式配置pinctrl并指定spi0_pins否则即使驱动加载成功硬件也不会将MIO[10:13]等引脚切换到SPI模式。以下是Zynq-7020平台SPI0的最小可行设备树片段system-top.dtsspi0 { status okay; compatible arm,pl022, arm,primecell; reg 0x0 0xe0006000 0x0 0x1000; interrupts 0 49 4; clocks clkc 27, clkc 28; /* apb_pclk, spi_clk */ clock-names apb_pclk, spi_clk; #address-cells 1; #size-cells 0; pinctrl-names default; pinctrl-0 spi0_pins; /* 片选信号需额外声明PL022硬件CS在Zynq上不推荐使用 */ cs-gpios gpio0 10 0, gpio0 11 0; spidev0 { compatible rohm,dh2228fv; reg 0; spi-max-frequency 50000000; /* 此处添加自定义属性供驱动读取 */ zynq,use-gpio-cs; }; };参数说明regSPI0控制器基地址为0xe0006000Zynq TRM Table 5-1长度0x1000clocks第一个时钟apb_pclk用于寄存器访问第二个spi_clk为SPI主时钟源需在ps7_init.tcl中配置为100MHzcs-gpios声明两个GPIO片选MIO[10]、MIO[11]驱动中通过gpiod_get()获取并控制zynq,use-gpio-cs自定义属性驱动在pl022_probe()中解析此字段以决定是否启用GPIO片选模式。2.2 驱动源码核心补丁修复Zynq特有的时钟与DMA缺陷原始spi-pl022.c在Zynq平台存在两处致命缺陷时钟使能顺序错误pl022_of_probe()中先调用clk_get()获取时钟但未检查clk_prepare_enable()返回值若spi_clk未在PS初始化阶段使能后续读SSIRSlave Select Inactive Register会返回0导致驱动误判为无从设备DMA通道地址映射缺失Zynq PS DMA的AXI地址空间与PL022的DMA请求线不匹配pl022_dma_setup()中dma_slave_config的device_fc字段需设为true且src_addr_width/dst_addr_width必须为DMA_SLAVE_BUSWIDTH_4_BYTES。本资源提供的zynq_spi_pl022_patch.diff关键修改如下// drivers/spi/spi-pl022.c 补丁节选 static int pl022_of_probe(struct amba_device *adev, const struct amba_id *id) { struct pl022 *pl022; struct device_node *np adev-dev.of_node; int ret; pl022 devm_kzalloc(adev-dev, sizeof(*pl022), GFP_KERNEL); if (!pl022) return -ENOMEM; // 【新增】Zynq专用时钟使能检查 pl022-clk devm_clk_get(adev-dev, spi_clk); if (IS_ERR(pl022-clk)) { dev_err(adev-dev, could not get spi_clk\n); return PTR_ERR(pl022-clk); } ret clk_prepare_enable(pl022-clk); if (ret) { dev_err(adev-dev, failed to enable spi_clk: %d\n, ret); return ret; } // 原有probe逻辑... } static int pl022_dma_setup(struct pl022 *pl022) { struct dma_slave_config config; int ret; memset(config, 0, sizeof(config)); config.direction DMA_MEM_TO_DEV; config.device_fc true; // 【关键】Zynq必须启用flow control config.src_addr_width DMA_SLAVE_BUSWIDTH_4_BYTES; config.dst_addr_width DMA_SLAVE_BUSWIDTH_4_BYTES; config.src_maxburst 4; config.dst_maxburst 4; config.src_addr pl022-phybase SSPDR; // SSP Data Register config.dst_addr pl022-phybase SSPDR; ret dmaengine_slave_config(pl022-dma_tx, config); if (ret) { dev_err(pl022-adev-dev, tx dma config failed: %d\n, ret); return ret; } // 同理配置rx dma... }逻辑说明时钟补丁确保spi_clk在驱动probe早期即被使能避免因时钟门控导致寄存器读写异常device_fc true启用Zynq DMA的AXI流控Flow Control防止SPI FIFO溢出时DMA仍持续写入src_addr_width/dst_addr_width设为4字节对齐因Zynq PS端AXI总线要求DMA传输地址必须4字节对齐否则触发AXI_SLAVE_ERROR。3. 编译与加载流程从内核源码到/dev/spidev0.0本资源提供两种集成方式静态编译进内核推荐用于量产固件或动态模块加载适合调试阶段。无论哪种方式都必须确保设备树二进制文件.dtb与内核版本严格匹配否则of_find_compatible_node()将找不到spi0节点。3.1 内核配置必须启用的关键选项在make menuconfig中以下选项必须设为y或mDevice Drivers --- [*] SPI support --- * SPI master controller drivers --- * ARM PL022 SSP controller * User mode SPI device driver support # spidev * SPI Protocol Masters --- * SPI EEPROMs from Atmel AT25 [*] Pin controllers --- * Xilinx Zynq pin controller注意SPI EEPROMs from Atmel AT25虽非必需但其驱动at25.c包含完整的spi_write_then_read()测试例程可直接复用验证SPI链路。3.2 静态编译修改Kbuild并重新构建内核将本资源中的spi-pl022-zynq.c已集成补丁的完整驱动放入drivers/spi/目录并修改同目录下Kbuild文件在末尾添加obj-$(CONFIG_SPI_PL022) spi-pl022-zynq.o然后执行标准内核构建流程# 进入内核源码根目录 cd /path/to/linux-source # 加载Zynq配置如zynq_defconfig make ARCHarm zynq_defconfig # 启用SPI_PL022确保.config中CONFIG_SPI_PL022y make ARCHarm menuconfig # 手动勾选 # 编译内核与设备树 make ARCHarm CROSS_COMPILEarm-linux-gnueabihf- -j$(nproc) make ARCHarm CROSS_COMPILEarm-linux-gnueabihf- dtbs # 输出arch/arm/boot/zImage 和 arch/arm/boot/dts/zynq-xxx.dtb参数说明ARCHarmZynq-7000为ARM Cortex-A9非aarch64CROSS_COMPILEarm-linux-gnueabihf-交叉编译工具链前缀需提前安装-j$(nproc)并行编译加速但若内存8GB建议改-j2防OOM。3.3 模块动态加载快速验证驱动功能若选择模块方式需先编译驱动模块# 创建Makefile与spi-pl022-zynq.c同目录 obj-m spi-pl022-zynq.o KDIR : /path/to/linux-source all: make -C $(KDIR) M$(PWD) modules clean: make -C $(KDIR) M$(PWD) clean编译后得到spi-pl022-zynq.ko通过insmod加载# 加载驱动模块需root权限 sudo insmod spi-pl022-zynq.ko # 检查是否成功注册master dmesg | tail -20 # 应看到pl022 e0006000.spi: mapped registers from 0xe0006000 to (ptr) # pl022 e0006000.spi: setup for 100000000 Hz on clk spi_clk # 查看生成的spidev设备 ls /dev/spi* # 正常输出/dev/spidev0.0 /dev/spidev0.1关键验证点dmesg中必须出现setup for XXX Hz证明时钟频率被正确解析/dev/spidev0.0存在表明spidev_probe()成功驱动已向SPI子系统注册设备若仅显示pl022 e0006000.spi: could not find pinctrl说明设备树pinctrl-0未正确定义。4. 常见问题排查五个血泪经验总结的必踩坑点Zynq PS SPI驱动调试中最容易卡住的环节往往不在C代码而在硬件抽象层设备树、时钟配置、引脚复用。以下是我们在某跨平台系统项目中反复验证的5个高频问题按现象→原因→解决的结构整理每一条都对应真实翻车现场。4.1 现象dmesg显示pl022 e0006000.spi: could not get spi_clk原因设备树中clocks属性指向的spi_clk名称与PS初始化脚本ps7_init.tcl中实际配置的时钟名不一致。Zynq PS的时钟树由ps7_init.tcl在FSBL阶段固化若该脚本中将SPI时钟命名为spi_ref_clk而设备树写spi_clk则clk_get()必然失败。解决反编译BOOT.BIN中的ps7_init.c搜索XIL_CLOCK_SET_RATE确认SPI时钟真实名称修改设备树clocks属性例如改为clkc 27索引27对应spi_ref_clk重新生成.dtb并烧录。4.2 现象/dev/spidev0.0存在但spidev_test -D /dev/spidev0.0返回Invalid argument原因spidev_test工具默认使用SPI_IOC_MESSAGE(1)ioctl而Zynq PL022驱动在DMA模式下未正确处理单次spi_message的complete回调导致wait_for_completion_timeout()超时。解决在驱动中pl022_transfer_one_message()函数末尾强制添加complete(pl022-xfer_done)即使DMA未完成或改用spidev_fdx.c本资源附带进行全双工测试该程序绕过ioctl直接调用write()/read()系统调用。4.3 现象SPI通信时数据错乱dmesg出现pl022 e0006000.spi: timeout waiting for TX to finish原因PL022的SSIRSlave Select Inactive Register值设置不当。Zynq TRM规定SSIR决定片选信号在传输结束后的保持时间若设为0默认值片选可能在最后一个字节发送完毕前就撤销导致从设备采样错误。解决在驱动pl022_setup()函数中写入SSIR寄存器writel(0x10, pl022-virtbase SSIR); // 保持16个时钟周期或在设备树spidev0节点中添加zynq,ssir 0x10驱动probe时读取并配置。4.4 现象DMA传输偶尔成功但dmesg频繁打印DMA transfer timeout原因Zynq PS端DMA的HP0通道SPI0专用被其他外设如SD卡控制器抢占导致SPI DMA请求延迟超过PL022 FIFO超时阈值默认16个时钟周期。解决在ps7_init.tcl中将HP0通道优先级设为最高set_property -dict {CONFIG.PRIORITY 7} [get_bd_cells ps7_i/HP0]或在驱动中降低DMA突发长度maxburst将pl022_dma_setup()中config.src_maxburst 2。4.5 现象spidev0.0可读写但连接逻辑分析仪发现MOSI无信号输出原因MIO引脚未配置为SPI功能。Zynq的MIO[10:13]默认为GPIO必须通过pinctrl明确切换为spi0_mosi/spi0_miso等。设备树中pinctrl-0 spi0_pins只是声明还需在pinctrl节点下定义spi0_pins。解决在设备树中补充pinctrl定义pinctrl { spi0_pins: spi0-pins { mux { function spi0; groups spi0_mosi, spi0_miso, spi0_sclk, spi0_ss; }; }; };确保groups中列出的spi0_mosi等名称与zynq-pinctrl.c中定义的组名完全一致大小写敏感。5. 实战验证用裸机bin文件定位硬件层故障当驱动层面一切看似正常但SPI通信仍不稳定时必须下沉到硬件层验证。本资源附带一个spi_loopback.bin裸机测试程序基于Xilinx SDK生成它绕过Linux内核直接在FSBL后运行通过寄存器操作实现SPI回环测试——这是定位Zynq PS SPI硬件故障的终极手段。5.1 裸机测试原理寄存器直写与状态轮询spi_loopback.bin的核心逻辑是使能SPI0时钟写SLCR寄存器0xf8000100配置MIO[10:13]为SPI功能写MIO_PIN_XX寄存器初始化PL022寄存器CR0设置数据宽度8bit、CPOL0、CPHA0CPSR预分频器设为2100MHz→50MHzIMSC使能TX、RX中断将测试数据0x55AA写入DR寄存器轮询SR寄存器RXFE0接收FIFO非空后读取DR。该过程完全不依赖Linux驱动若spi_loopback.bin能稳定回传数据则证明Zynq PS SPI硬件链路时钟、引脚、控制器IP完好问题必在驱动或设备树若裸机也失败则需检查PS初始化脚本或硬件焊接。5.2 烧录与运行裸机bin的完整步骤# 1. 将spi_loopback.bin放入BOOT.BIN生成目录 cp spi_loopback.bin /path/to/bootgen/ # 2. 修改bootgen.bif添加裸机程序 the_ROM_image: { [bootloader]fsbl.elf [pmufw_image]pmufw.elf [destination_cpua53-0, exception_levelel-3, trustzone]bl31.elf [destination_cpua53-0, exception_levelel-2]u-boot.elf [destination_cpua53-0, exception_levelel-1]spi_loopback.bin # 新增 } # 3. 生成BOOT.BIN bootgen -image bootgen.bif -arch zynq -process # 4. 烧录至SD卡启动后串口将打印 # SPI Loopback Test: Sending 0x55AA... Received 0x55AA PASS # SPI Loopback Test: Sending 0xAA55... Received 0xAA55 PASS关键参数spi_loopback.bin必须放在u-boot.elf之后确保其加载地址不与U-Boot冲突默认0x100000测试数据0x55AA选用具有高/低电平交替的模式便于逻辑分析仪观察波形完整性。5.3 故障隔离决策树从现象反推问题层级当SPI通信异常时按此顺序执行验证可快速定位故障域验证步骤预期结果结论1. 运行spi_loopback.bin串口打印PASS硬件层OK → 问题在Linux驱动或设备树2.dmesg | grep spi查看驱动probe日志显示setup for 100000000 Hz驱动初始化OK → 问题在spidev用户态或应用层3.cat /sys/class/spi_master/spi0/device/name输出pl022设备树节点被正确解析 → 问题在GPIO片选或从设备硬件4. 用逻辑分析仪抓取MOSI/MISO波形MOSI有数据MISO无响应从设备未上电或片选信号未拉低检查cs-gpios配置注意若spi_loopback.bin测试失败立即停止Linux驱动调试。此时应检查ps7_init.tcl中SPI时钟是否被禁用、MIO引脚是否被其他外设占用如UART1与SPI0共享MIO[10]、或PCB上SPI走线是否存在短路/虚焊。从那以后我每次接到Zynq SPI需求第一件事就是用spi_loopback.bin跑通裸机回环——这比在Linux里调三天驱动日志更高效。因为硬件层的问题永远无法用软件补丁彻底掩盖而一个稳定的裸机测试就是驱动开发最硬的底气。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑