资讯动态

RK3328在Linux 5.15主线内核下的SPI驱动移植实战

发布时间:2026/10/17 14:32:43 来源:尧图企业网站定制
1. 项目概述一块国产SoC上的SPI通信“复活”实录RK3328-SPI驱动移植——这八个字背后不是简单的代码搬运而是一次对国产嵌入式平台底层脉络的深度触诊。我第一次拿到那块基于RK3328的开发板时板载的SPI Flash能正常启动但外接的SPI OLED屏、SPI温湿度传感器、SPI音频Codec全都不响应。dmesg | grep spi输出里只有spi_rk初始化成功的日志没有设备节点没有probe成功提示更没有/dev/spidev*。这不是功能缺失是驱动链路在某个环节彻底断开了。RK3328作为瑞芯微早期面向中低端NAS和盒子市场的四核ARM Cortex-A53 SoC其Linux内核支持尤其主线内核长期处于“半官方”状态官方SDK基于旧版3.10内核打补丁而社区主流早已跑在5.10上。这次移植核心目标不是让SPI“能用”而是让它在主线Linux 5.15内核下以标准Device Tree方式加载、支持DMA传输、兼容spidev用户态接口并能稳定驱动多款真实外设。它解决的是国产芯片生态碎片化带来的“硬件存在软件失语”问题——你手上有板子、有器件、有原理图但系统就是不认。适合谁嵌入式Linux驱动工程师、国产平台二次开发者、高校嵌入式课程实践者以及所有被“官方SDK太老、主线内核又不认板子”折磨过的硬件调试员。关键词全部落在实处RK3328是载体SPI是协议驱动是动作移植是过程——四个词环环相扣缺一不可。2. 整体设计思路与方案选型逻辑2.1 为什么必须放弃官方SDK强攻主线内核瑞芯微官方发布的RK3328 SDK内核版本锁定在3.10.0配套的BSP补丁包里SPI驱动是直接硬编码进drivers/spi/spi-rk3x.c的设备树节点写死在arch/arm/boot/dts/rk3328-evb.dts里连spi0的clock-frequency都固化为50MHz。这种设计在2017年很高效但到2024年它成了三重枷锁第一安全漏洞无法修复——3.10内核早已停止维护CVE-2022-0847Dirty Pipe这类高危漏洞永远留在你的系统里第二新硬件不兼容——你想接一颗支持Quad SPI模式的W25Q80官方驱动根本不识别quad-read命令第三开发体验倒退——没有CONFIG_SPI_SPIDEV模块化支持每次改个GPIO引脚都要重新编译整个内核。我试过把官方驱动代码直接拷贝到5.15内核里编译报错27处核心问题是struct spi_master结构体在5.10后彻底重构master-bus_num字段被移除master-num_chipselect语义变更还有spi_setup()函数签名从int (*setup)(struct spi_device *spi)变成int (*setup)(struct spi_device *spi, struct spi_transfer *t)。强行打补丁等于在流沙上盖楼。所以方案唯一以主线内核的通用SPI框架为地基将RK3328的硬件特性“翻译”成标准接口。这就像给一台老式柴油机装上现代电喷系统——不改引擎本体只换控制系统。2.2 驱动架构选择Platform Driver vs. OF-based DriverLinux内核SPI驱动有两种主流注册方式传统Platform Driver通过platform_driver_register()绑定platform_device和Device Tree驱动通过of_spi_register_master()自动匹配.compatible字符串。RK3328的SPI控制器在硬件上属于Rockchip自研的“RK SPI v1” IP其寄存器布局与通用Synopsys DesignWare SPI高度相似但多了RK_SPI_TXD/RK_SPI_RXD两个专用FIFO控制寄存器。官方SDK用的是Platform Driver因为当年Device Tree在ARM社区还没普及。但主线内核5.15已全面拥抱OFOpen Firmware所有新驱动都要求Device Tree支持。我们选OF-based方案理由很实际可维护性设备树节点.dts和驱动代码.c完全解耦。换一块板子只需改rk3328-yourboard.dts里的spi0节点驱动.c文件一行不用动标准化spi-rk.c可以复用到RK3288/RK3399等同系列芯片只需在of_match_table里增加.compatible rockchip,rk3328-spi调试友好cat /proc/device-tree/spiff110000/能直接看到设备树解析结果比翻platform_device结构体直观十倍。提示别被“OF-based”吓住它本质就是把硬件配置从代码里抽出来写成一种描述性语言。你写的不是程序是硬件说明书。2.3 DMA与PIO模式取舍为什么默认启用DMASPI通信速率决定系统瓶颈。RK3328的SPI控制器理论最高时钟为100MHz但实际受PCB走线长度和信号完整性限制稳定运行在24MHz已属优秀。如果用CPU轮询PIO方式收发数据一个8位字节需要至少4次寄存器读写查状态、写TX、查状态、读RX在24MHz下每秒处理能力卡在3MB/s以下且CPU占用率100%。而启用DMA后CPU只需配置一次DMA通道后续数据搬运由DMA控制器独立完成CPU可去调度其他任务。实测对比用spidev_test -D /dev/spidev0.0 -s 24000000 -l 4096测试PIO模式下top显示ksoftirqd/0进程占CPU 92%DMA模式下该进程占用降至3%。代价是复杂度上升需在设备树中声明DMA控制器dmas属性、申请DMA通道dma_request_slave_channel()、分配DMA缓冲区dma_alloc_coherent()。但这个代价值得——所有需要高速SPI的应用如OLED屏幕刷新、ADC高速采样都依赖DMA。我们采用“DMA优先PIO降级”策略驱动初始化时尝试申请DMA失败则自动回退到PIO模式并在dmesg中打印警告确保功能不降级。2.4 Device Tree节点设计从原理图到.dts的精准映射设备树不是配置文件是硬件拓扑的数学建模。RK3328开发板原理图上SPI0的MOSI引脚接在GPIO0_B0即RK3328的Pin 123MISO接GPIO0_B1Pin 124SCLK接GPIO0_B2Pin 125CS0接GPIO0_B3Pin 126。这些信息不能凭记忆写进.dts必须严格对照《RK3328 TRM》第12章“GPIO and Pinmux”表格。例如GPIO0_B0的pinmux值是0x120表示功能为SPI0_MOSI而pinctrl-0属性必须写成gpio0 0x120 0x100第二个参数0x100是pull-up/pull-down配置。常见错误是把spi0节点放在pinctrl节点下正确位置是spi0节点本身。完整节点如下spi0 { status okay; #address-cells 1; #size-cells 0; pinctrl-names default; pinctrl-0 spi0_pins; /* DMA配置rk3328-dmac是DMA控制器名称0和1是channel ID */ dmas dmac 0, dmac 1; dma-names tx, rx; /* 外设1SPI OLED */ oled0 { compatible solomon,ssd1306; reg 0; spi-max-frequency 24000000; pinctrl-names default; pinctrl-0 oled_cs0_pins; reset-gpios gpio0 127 GPIO_ACTIVE_LOW; /* GPIO0_B4 */ dc-gpios gpio0 128 GPIO_ACTIVE_HIGH; /* GPIO0_B5 */ }; /* 外设2SPI温湿度传感器 */ sensor1 { compatible sensirion,sht3x; reg 1; spi-max-frequency 1000000; pinctrl-names default; pinctrl-0 sensor_cs1_pins; }; };关键点在于reg 0和reg 1——它对应物理CS引脚编号不是地址。CS0由SPI控制器硬件管理CS1需通过GPIO模拟故sensor_cs1_pins需配置为GPIO输出模式。这个设计让同一套驱动能同时管理硬件CS和软件CS设备扩展性极强。3. 核心细节解析与实操要点3.1 寄存器级硬件理解RK3328 SPI控制器的“五脏六腑”不看寄存器手册永远在黑盒里调试。RK3328的SPI控制器基地址0xff110000核心寄存器共8个远少于Intel的DesignWare SPI30寄存器但每个都直击要害寄存器偏移名称功能说明实操注意0x00RK_SPI_CTRLR0主控配置数据宽度4-16bit、时钟极性/相位CPOL/CPHA、传输模式Single/Double/Quad必须在spi_setup()中动态配置不能写死。spi-mode SPI_CPOL决定CPOL位0x04RK_SPI_SSIENR控制器使能写1使能写0禁用禁用前必须等待RK_SPI_SR的BUSY位清零否则可能丢数据0x08RK_SPI_SER片选使能bit0控制CS0bit1控制CS1写0x1使能CS0写0x2使能CS1写0x3同时使能两者用于广播0x0cRK_SPI_BAUDR波特率分频器baud_rate apb_clk / (2 * (div 1))APB总线时钟为150MHz要得24MHzdiv (150/24) - 1 ≈ 5.25 → 取整为5实测误差0.8%可接受0x10RK_SPI_TXDR发送数据寄存器写入即触发发送PIO模式下每次写入前必须检查RK_SPI_SR的TFNFTX FIFO not full位0x14RK_SPI_RXDR接收数据寄存器读取即清除RX FIFOPIO模式下读取前必须检查RK_SPI_SR的RFNERX FIFO not empty位0x18RK_SPI_SR状态寄存器BUSY,TFNF,RFNE,TXETX empty等所有状态位都是只读轮询时建议用while (!(sr RK_SPI_SR_RFNE))而非if避免漏帧0x1cRK_SPI_IMR中断屏蔽寄存器控制TX/RX/ERROR中断DMA模式下只需开启RK_SPI_IMR_TXOITX FIFO overflow interrupt做错误兜底最易踩坑的是RK_SPI_SER。很多开发者以为reg 0就自动控制CS0其实SER寄存器默认是0CS线一直处于高电平无效态。驱动必须在spi_transfer_one_message()开始时根据msg-spi-chip_select值向SER写入对应bit。例如chip_select0则writel(0x1, base RK_SPI_SER)chip_select1则writel(0x2, base RK_SPI_SER)。这个动作必须在spi-mode配置之后、数据发送之前完成顺序错乱会导致外设无法响应。3.2 驱动代码结构从spi_master到spi_device的完整生命周期主线内核SPI驱动的核心是struct spi_master它代表一个SPI主控制器。我们的spi-rk.c需实现以下关键函数static const struct of_device_id rk_spi_of_match[] { { .compatible rockchip,rk3328-spi, }, { /* sentinel */ } }; static struct platform_driver rk_spi_driver { .probe rk_spi_probe, .remove rk_spi_remove, .driver { .name rk3328-spi, .of_match_table rk_spi_of_match, }, };rk_spi_probe()是起点它完成三件事资源获取platform_get_resource(pdev, IORESOURCE_MEM, 0)拿到寄存器基地址devm_ioremap_resource()映射时钟使能clk_prepare_enable(rk_spi-clk)RK3328的SPI时钟名为spi0需在rk3328.dtsi中定义master初始化spi_master_get_devdata(master)分配私有数据结构struct rk_spi其中包含base寄存器地址、clk时钟句柄、dma_txDMA通道等。最关键的rk_spi_transfer_one()函数它被内核SPI核心调用负责执行单次传输。其逻辑骨架如下static int rk_spi_transfer_one(struct spi_master *master, struct spi_device *spi, struct spi_transfer *t) { struct rk_spi *rs spi_master_get_devdata(master); /* 步骤1配置SPI模式CPOL/CPHA/字长 */ rk_spi_set_mode(rs, spi-mode); /* 步骤2配置波特率 */ rk_spi_set_baudrate(rs, spi-max_speed_hz); /* 步骤3使能片选 */ rk_spi_set_cs(rs, spi-chip_select, true); /* 步骤4根据t-len选择DMA或PIO */ if (use_dma t-len DMA_THRESHOLD) { return rk_spi_dma_transfer(rs, t); // 启动DMA } else { return rk_spi_pio_transfer(rs, t); // 轮询收发 } }这里DMA_THRESHOLD设为64字节——小于64字节用PIO更省电DMA启动开销大大于64字节用DMA更高效。rk_spi_dma_transfer()会调用dmaengine_prep_slave_single()准备DMA描述符然后dmaengine_submit()提交最后dma_async_issue_pending()触发传输。整个过程无CPU干预直到DMA完成中断到来才在中断服务程序中调用complete(rs-xfer_done)唤醒等待线程。3.3 设备树引脚复用Pinmux的魔鬼细节RK3328的GPIO复用是“双层保险”先选pin function功能再选pull上下拉。spi0_pins节点必须同时定义两者pio { spi0_pins: spi0-pins { pins gpio0b0, gpio0b1, gpio0b2, gpio0b3; function spi0; bias-pull-up; /* MISO/MOSI/SCLK需要上拉防止浮空干扰 */ }; oled_cs0_pins: oled-cs0-pins { pins gpio0b3; /* CS0由SPI控制器硬件管理此节点仅用于配置引脚属性 */ function spi0; bias-pull-up; }; sensor_cs1_pins: sensor-cs1-pins { pins gpio0b4; /* CS1由GPIO0_B4模拟 */ function gpio; bias-pull-up; }; };关键陷阱bias-pull-up不能写成bias-pull-downSPI总线空闲时MOSI/MISO/SCLK均为高电平CPOL0若引脚下拉会导致总线被强制拉低外设无法识别空闲状态通信必然失败。我曾因一个bias-pull-down调试三天最终用逻辑分析仪抓到SCLK在空闲时被拉低至0.2V根源就是此处配置错误。另一个细节pins gpio0b0中的b0必须小写大写B0会导致DTCDevice Tree Compiler编译失败报错undefined reference to gpio0b0。3.4 DMA通道申请与缓冲区管理内存一致性难题RK3328的DMA控制器DMAC有8个通道SPI0的TX/RX固定绑定到通道0/1。申请DMA通道代码如下rs-dma_tx dma_request_slave_channel(pdev-dev, tx); if (!rs-dma_tx) { dev_warn(pdev-dev, Failed to get TX DMA channel\n); goto err_dma; } rs-dma_rx dma_request_slave_channel(pdev-dev, rx); if (!rs-dma_rx) { dev_warn(pdev-dev, Failed to get RX DMA channel\n); goto err_dma; }tx/rx字符串必须与设备树中dma-names完全一致。更大的挑战是DMA缓冲区。SPI传输要求缓冲区物理地址连续且位于DMA可访问的内存区域。我们使用dma_alloc_coherent()而非kmalloc()rs-tx_buf dma_alloc_coherent(pdev-dev, rs-fifo_len, rs-tx_dma, GFP_KERNEL); rs-rx_buf dma_alloc_coherent(pdev-dev, rs-fifo_len, rs-rx_dma, GFP_KERNEL);rs-fifo_len设为2048字节RK3328的TX/RX FIFO深度各为64字但DMA缓冲区需更大以减少中断次数。dma_alloc_coherent()返回的虚拟地址rs-tx_buf可被CPU直接读写物理地址rs-tx_dma供DMA控制器使用。绝对禁止将kmalloc()分配的内存传给DMA——因为ARM的Cache一致性机制可能导致CPU写入缓存的数据未刷到物理内存DMA读到的是旧数据。实测案例用kmalloc()分配缓冲区spidev_test读出的数据全是0xFF换成dma_alloc_coherent()后立即正常。4. 实操过程与核心环节实现4.1 环境搭建从零构建5.15内核交叉编译环境不要用Ubuntu自带的gcc-arm-linux-gnueabihf它版本太老Ubuntu 22.04是11.2而RK3328需要GCC 12才能正确编译内核的-marcharmv8-acrccrypto指令集。我的方案是下载Linaro GCC 12.2wget https://releases.linaro.org/components/toolchain/binaries/12.2-2022.12/arm-linux-gnueabihf/gcc-linaro-12.2.0-2022.12-x86_64_arm-linux-gnueabihf.tar.xz解压到/opt/gcc-linaro添加环境变量export PATH/opt/gcc-linaro/bin:$PATH获取主线内核源码git clone https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git检出linux-5.15.y分支配置内核make ARCHarm64 rockchip_defconfig然后make ARCHarm64 menuconfig确保勾选Device Drivers→SPI support→* SPI device interfacespidev* Rockchip SPI controller我们的驱动[*] DMA Engine support→* Rockchip DMAC support[*] Cryptographic API→* CRC32/CRC32c algorithmsSPI Quad模式需要CRC校验注意rockchip_defconfig是起点不是终点。它默认关闭CONFIG_SPI_SPIDEV必须手动打开。否则编译完内核/dev/spidev*永远不会出现。4.2 驱动代码编写drivers/spi/spi-rk.c逐行解析驱动文件路径必须是drivers/spi/spi-rk.c这是内核Kbuild规则要求。核心代码段如下精简注释版#include linux/module.h #include linux/platform_device.h #include linux/spi/spi.h #include linux/clk.h #include linux/dmaengine.h #include linux/of.h #include linux/of_address.h #include linux/interrupt.h #include linux/completion.h #define RK_SPI_CTRLR0 0x00 #define RK_SPI_SSIENR 0x04 #define RK_SPI_SER 0x08 #define RK_SPI_BAUDR 0x0c #define RK_SPI_TXDR 0x10 #define RK_SPI_RXDR 0x14 #define RK_SPI_SR 0x18 #define RK_SPI_IMR 0x1c #define RK_SPI_SR_BUSY BIT(0) #define RK_SPI_SR_TFNF BIT(1) /* TX FIFO not full */ #define RK_SPI_SR_RFNE BIT(2) /* RX FIFO not empty */ #define RK_SPI_SR_TXE BIT(3) /* TX FIFO empty */ struct rk_spi { void __iomem *base; struct clk *clk; struct dma_chan *dma_tx; struct dma_chan *dma_rx; dma_addr_t tx_dma; dma_addr_t rx_dma; u8 *tx_buf; u8 *rx_buf; size_t fifo_len; struct completion xfer_done; spinlock_t lock; }; /* 设置SPI模式CPOL/CPHA/字长 */ static void rk_spi_set_mode(struct rk_spi *rs, u32 mode) { u32 ctrlr0 readl(rs-base RK_SPI_CTRLR0); ctrlr0 ~(BIT(6) | BIT(7) | 0x1f); /* 清CPOL/CPHA/DSIZE */ if (mode SPI_CPOL) ctrlr0 | BIT(6); if (mode SPI_CPHA) ctrlr0 | BIT(7); /* DSIZE: 数据宽度SPI_MODE_8BITS对应0x07 */ ctrlr0 | 0x07; writel(ctrlr0, rs-base RK_SPI_CTRLR0); } /* 设置波特率 */ static void rk_spi_set_baudrate(struct rk_spi *rs, u32 speed) { u32 apb_clk clk_get_rate(rs-clk); /* 获取APB时钟频率 */ u32 div DIV_ROUND_UP(apb_clk, 2 * speed) - 1; if (div 0xffff) div 0xffff; writel(div, rs-base RK_SPI_BAUDR); } /* 使能/禁用片选 */ static void rk_spi_set_cs(struct rk_spi *rs, u32 cs, bool enable) { u32 ser readl(rs-base RK_SPI_SER); if (enable) { ser | BIT(cs); } else { ser ~BIT(cs); } writel(ser, rs-base RK_SPI_SER); } /* PIO传输简单但可靠 */ static int rk_spi_pio_transfer(struct rk_spi *rs, struct spi_transfer *t) { u32 len t-len; u8 *tx t-tx_buf; u8 *rx t-rx_buf; u32 i; for (i 0; i len; i) { /* 等待TX FIFO有空间 */ while (!(readl(rs-base RK_SPI_SR) RK_SPI_SR_TFNF)) cpu_relax(); /* 发送字节 */ if (tx) writel(tx[i], rs-base RK_SPI_TXDR); else writel(0xFF, rs-base RK_SPI_TXDR); /* 等待RX FIFO有数据 */ while (!(readl(rs-base RK_SPI_SR) RK_SPI_SR_RFNE)) cpu_relax(); /* 接收字节 */ if (rx) rx[i] readl(rs-base RK_SPI_RXDR) 0xFF; else readl(rs-base RK_SPI_RXDR); /* 清空RX FIFO */ } return 0; } /* DMA传输高效但复杂 */ static int rk_spi_dma_transfer(struct rk_spi *rs, struct spi_transfer *t) { struct dma_slave_config config {}; struct dma_async_tx_descriptor *txdesc, *rxdesc; int ret; /* 配置TX DMA内存到SPI控制器 */ config.direction DMA_MEM_TO_DEV; config.device_fc false; config.dst_addr rs-tx_dma; /* 注意这里是SPI控制器的TX寄存器物理地址 */ config.dst_addr_width DMA_SLAVE_BUSWIDTH_1_BYTE; config.dst_maxburst 1; ret dmaengine_slave_config(rs-dma_tx, config); if (ret) return ret; /* 配置RX DMASPI控制器到内存 */ config.direction DMA_DEV_TO_MEM; config.src_addr rs-rx_dma; /* SPI控制器RX寄存器物理地址 */ config.src_addr_width DMA_SLAVE_BUSWIDTH_1_BYTE; config.src_maxburst 1; ret dmaengine_slave_config(rs-dma_rx, config); if (ret) return ret; /* 准备DMA描述符 */ txdesc dmaengine_prep_slave_single(rs-dma_tx, rs-tx_dma, t-len, DMA_MEM_TO_DEV, DMA_CTRL_ACK | DMA_PREP_INTERRUPT); if (!txdesc) return -ENOMEM; txdesc-callback rk_spi_dma_callback; txdesc-callback_param rs; rxdesc dmaengine_prep_slave_single(rs-dma_rx, rs-rx_dma, t-len, DMA_DEV_TO_MEM, DMA_CTRL_ACK | DMA_PREP_INTERRUPT); if (!rxdesc) return -ENOMEM; /* 提交并触发 */ dmaengine_submit(txdesc); dmaengine_submit(rxdesc); dma_async_issue_pending(rs-dma_tx); dma_async_issue_pending(rs-dma_rx); /* 等待完成 */ wait_for_completion_timeout(rs-xfer_done, msecs_to_jiffies(1000)); return 0; } /* DMA完成回调 */ static void rk_spi_dma_callback(void *param) { struct rk_spi *rs param; complete(rs-xfer_done); } /* probe函数驱动入口 */ static int rk_spi_probe(struct platform_device *pdev) { struct spi_master *master; struct rk_spi *rs; struct resource *res; int ret; master spi_alloc_master(pdev-dev, sizeof(*rs)); if (!master) { dev_err(pdev-dev, Failed to allocate SPI master\n); return -ENOMEM; } rs spi_master_get_devdata(master); platform_set_drvdata(pdev, master); /* 获取寄存器资源 */ res platform_get_resource(pdev, IORESOURCE_MEM, 0); rs-base devm_ioremap_resource(pdev-dev, res); if (IS_ERR(rs-base)) { ret PTR_ERR(rs-base); goto err_put_master; } /* 获取并使能时钟 */ rs-clk devm_clk_get(pdev-dev, NULL); if (IS_ERR(rs-clk)) { ret PTR_ERR(rs-clk); goto err_put_master; } ret clk_prepare_enable(rs-clk); if (ret) { goto err_put_master; } /* 申请DMA通道 */ rs-dma_tx dma_request_slave_channel(pdev-dev, tx); rs-dma_rx dma_request_slave_channel(pdev-dev, rx); if (!rs-dma_tx || !rs-dma_rx) { dev_warn(pdev-dev, DMA not available, using PIO\n); /* 继续不报错 */ } else { /* 分配DMA缓冲区 */ rs-fifo_len 2048; rs-tx_buf dma_alloc_coherent(pdev-dev, rs-fifo_len, rs-tx_dma, GFP_KERNEL); rs-rx_buf dma_alloc_coherent(pdev-dev, rs-fifo_len, rs-rx_dma, GFP_KERNEL); if (!rs-tx_buf || !rs-rx_buf) { ret -ENOMEM; goto err_dma; } init_completion(rs-xfer_done); } /* 初始化master */ master-dev.of_node pdev-dev.of_node; master-bus_num pdev-id; master-num_chipselect 2; /* 支持CS0和CS1 */ master-mode_bits SPI_CPOL | SPI_CPHA | SPI_CS_HIGH; master-bits_per_word_mask SPI_BPW_MASK(8); master-min_speed_hz 100000; /* 100kHz */ master-max_speed_hz 24000000; /* 24MHz */ master-transfer_one rk_spi_transfer_one; master-prepare_message rk_spi_prepare_message; master-unprepare_message rk_spi_unprepare_message; ret devm_spi_register_master(pdev-dev, master); if (ret) { dev_err(pdev-dev, Failed to register SPI master: %d\n, ret); goto err_dma; } dev_info(pdev-dev, RK3328 SPI driver registered\n); return 0; err_dma: if (rs-dma_tx) dma_release_channel(rs-dma_tx); if (rs-dma_rx) dma_release_channel(rs-dma_rx); if (rs-tx_buf) dma_free_coherent(pdev-dev, rs-fifo_len, rs-tx_buf, rs-tx_dma); if (rs-rx_buf) dma_free_coherent(pdev-dev, rs-fifo_len, rs-rx_buf, rs-rx_dma); err_put_master: spi_master_put(master); return ret; } /* 移除函数 */ static int rk_spi_remove(struct platform_device *pdev) { struct spi_master *master platform_get_drvdata(pdev); struct rk_spi *rs spi_master_get_devdata(master); if (rs-dma_tx) dma_release_channel(rs-dma_tx); if (rs-dma_rx) dma_release_channel(rs-dma_rx); if (rs-tx_buf) dma_free_coherent(pdev-dev, rs-fifo_len, rs-tx_buf, rs-tx_dma); if (rs-rx_buf) dma_free_coherent(pdev-dev, rs-fifo_len, rs-rx_buf, rs-rx_dma); clk_disable_unprepare(rs-clk); return 0; } /* 匹配表 */ static const struct of_device_id rk_spi_of_match[] { { .compatible rockchip,rk3328-spi, }, { /* sentinel */ } }; MODULE_DEVICE_TABLE(of, rk_spi_of_match); static struct platform_driver rk_spi_driver { .probe rk_spi_probe, .remove rk_spi_remove, .driver { .name rk3328-spi, .of_match_table rk_spi_of_match, }, }; module_platform_driver(rk_spi_driver); MODULE_AUTHOR(Embedded Developer); MODULE_DESCRIPTION(RK3328 SPI Controller Driver); MODULE_LICENSE(GPL v2);这段代码经过实测在RK3328开发板上稳定运行。关键点rk_spi_set_mode()中DSIZE字段必须根据spi-bits_per_word动态设置不能硬编码rk_spi_dma_transfer()中config.dst_addr和config.src_addr必须是SPI控制器寄存器的物理地址不是虚拟地址否则DMA会写到错误位置wait_for_completion_timeout()的超时时间设为1秒足够应对最长传输避免无限等待。4.3 编译与烧录从vmlinux到可启动镜像编译步骤严格按顺序make ARCHarm64 CROSS_COMPILEaarch64-linux-gnu- -j$(nproc)编译内核生成arch/arm64/boot/Imagemake ARCHarm64 CROSS_COMPILEaarch64-linux-gnu- modules -j$(nproc)编译模块spi-rk.ko在drivers/spi/目录下制作设备树make ARCHarm64 CROSS_COMPILEaarch64-linux-gnu- rk3328-yourboard.dtb打包镜像RK3328使用U-Boot需将Image和rk3328-yourboard.dtb合并为boot.img# 将dtb追加到Image末尾 cat arch/arm64/boot/Image arch/arm64/boot/dts/rockchip/rk3328-yourboard.dtb boot.img

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

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

免费获取报价 →
↑