1. RK平台SPI设备驱动开发概述
在嵌入式Linux开发领域,Rockchip(RK)平台因其出色的性价比和丰富的接口资源,成为众多智能硬件产品的首选方案。SPI(Serial Peripheral Interface)作为一种高速、全双工的同步串行通信接口,在RK平台上被广泛应用于显示屏控制、Flash存储、传感器连接等场景。本文将基于RK3568平台,深入剖析SPI设备驱动的开发流程,重点覆盖SPI屏幕和SPI Flash两类典型设备的驱动实现。
SPI驱动开发的核心在于理解Linux内核中的SPI子系统架构。RK平台采用标准Linux SPI框架,主要由三层构成:SPI核心层(提供总线注册、设备匹配等基础功能)、SPI控制器驱动(处理SoC特定的硬件操作)以及SPI设备驱动(实现具体外设功能)。开发者主要关注设备驱动层的实现,但需要了解控制器驱动的关键配置点。
提示:RK平台的SPI控制器驱动通常已由芯片厂商提供,位于drivers/spi/spi-rockchip.c。在开发设备驱动前,建议先确认该驱动已正确加载且硬件连接正常。
2. 开发环境准备与硬件配置
2.1 硬件连接检查
以RK3568开发板为例,其SPI控制器硬件特性如下:
- 支持4个独立SPI控制器(SPI0~SPI3)
- 最高时钟频率50MHz
- 支持DMA传输
- 可配置为Master或Slave模式
典型SPI屏幕连接方式:
code复制RK3568 SPI屏幕
SPIx_CLK --> SCLK
SPIx_MOSI --> SDI
SPIx_CS0 --> CS
GPIOx --> DC (数据/命令控制线)
GPIOy --> RESET (复位线)
SPI Flash连接相对简单,通常只需连接CLK、MOSI、MISO、CS四线。需要注意的是,部分SPI Flash需要将WP(写保护)和HOLD引脚上拉以避免意外锁定。
2.2 内核配置与设备树编写
确保内核已启用SPI支持:
bash复制make menuconfig
路径:Device Drivers -> SPI support -> Rockchip SPI controller
设备树节点示例(SPI屏幕):
dts复制&spi1 {
status = "okay";
pinctrl-names = "default";
pinctrl-0 = <&spi1m0_cs0 &spi1m0_pins>;
max-freq = <24000000>;
display@0 {
compatible = "ilitek,ili9341";
reg = <0>;
spi-max-frequency = <24000000>;
dc-gpios = <&gpio3 RK_PC1 GPIO_ACTIVE_HIGH>;
reset-gpios = <&gpio3 RK_PC2 GPIO_ACTIVE_LOW>;
rotation = <90>;
buswidth = <8>;
};
};
对于SPI Flash设备,通常使用内核标准的"jedec,spi-nor"兼容字符串,并需特别注意分区表的定义:
dts复制&spi0 {
status = "okay";
flash@0 {
compatible = "jedec,spi-nor";
reg = <0>;
spi-max-frequency = <50000000>;
partitions {
compatible = "fixed-partitions";
#address-cells = <1>;
#size-cells = <1>;
bootloader@0 {
label = "u-boot";
reg = <0x0 0x100000>;
};
kernel@100000 {
label = "kernel";
reg = <0x100000 0x800000>;
};
};
};
};
3. SPI屏幕驱动开发详解
3.1 驱动框架选择
Linux内核为显示设备提供了DRM(Direct Rendering Manager)和FBDEV(Frame Buffer Device)两套框架。对于SPI屏幕这类简单显示设备,通常选择实现FBDEV框架,因其开发复杂度较低且资源占用少。
驱动开发主要涉及以下结构体:
fb_info:帧缓冲设备的核心结构fb_ops:包含屏幕操作函数指针集spi_driver:SPI设备驱动注册结构
典型驱动初始化流程:
- 分配并初始化
fb_info结构 - 设置屏幕参数(分辨率、色深等)
- 实现
fb_ops中的关键操作(如设置寄存器、写入显存) - 注册SPI驱动并关联probe函数
3.2 关键操作实现
屏幕初始化序列通常需要严格按照datasheet的时序要求实现。以ILI9341控制器为例:
c复制static int ili9341_init_sequence(struct spi_device *spi)
{
/* 硬件复位 */
gpiod_set_value_cansleep(reset_gpio, 0);
msleep(20);
gpiod_set_value_cansleep(reset_gpio, 1);
msleep(120);
/* 发送初始化命令序列 */
const u8 init_commands[] = {
0xCF, 0x00, 0x83, 0x30,
0xED, 0x64, 0x03, 0x12, 0x81,
0xE8, 0x85, 0x01, 0x79,
// ... 更多初始化命令
};
for (int i = 0; i < sizeof(init_commands); ) {
u8 cmd = init_commands[i++];
u8 len = init_commands[i++];
ili9341_write_cmd(spi, cmd);
ili9341_write_data(spi, &init_commands[i], len);
i += len;
}
return 0;
}
显存更新操作需要特别注意性能优化。一种有效策略是实现fb_ops->fb_fillrect等加速接口:
c复制static void ili9341_fb_fillrect(struct fb_info *info,
const struct fb_fillrect *rect)
{
/* 设置更新区域 */
ili9341_set_window(spi, rect->dx, rect->dy,
rect->dx + rect->width - 1,
rect->dy + rect->height - 1);
/* 批量填充颜色数据 */
u16 color = ((rect->color >> 16) & 0xF800) |
((rect->color >> 8) & 0x07E0) |
(rect->color & 0x001F);
u16 *buf = kmalloc(rect->width * sizeof(u16), GFP_KERNEL);
for (int i = 0; i < rect->width; i++)
buf[i] = color;
for (int y = 0; y < rect->height; y++)
ili9341_write_data(spi, (u8 *)buf, rect->width * 2);
kfree(buf);
}
注意:SPI屏幕的刷新率受限于总线速度。对于320x240的16位色屏幕,24MHz SPI时钟下理论最大刷新率约为:
计算式:24000000/(32024016) ≈ 19.5fps
实际性能会因协议开销更低,需根据具体屏幕优化传输协议。
4. SPI Flash驱动开发实战
4.1 Flash特性与驱动选择
RK平台常用的SPI Flash主要有Winbond、GD、MXIC等品牌,容量从1Mb到128Mb不等。内核已提供标准的SPI NOR Flash驱动(drivers/mtd/spi-nor/),支持JEDEC标准的大多数芯片。
开发时需要重点关注:
- 芯片的页大小(通常256B/512B)
- 扇区/块擦除大小(4KB/32KB/64KB)
- 支持的指令集(标准SPI/Dual/Quad SPI)
- 写保护机制
驱动主要通过spi_nor结构体描述芯片特性:
c复制static const struct flash_info my_flash_parts[] = {
{
.name = "GD25Q128",
.id = {0xc8, 0x40, 0x18},
.id_len = 3,
.sector_size = SZ_64K,
.n_sectors = 256,
.page_size = 256,
.flags = SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,
},
// 更多芯片支持...
};
4.2 性能优化技巧
-
启用Quad SPI模式:
在设备树中配置quad模式并确保硬件连线正确:dts复制flash@0 { compatible = "jedec,spi-nor"; reg = <0>; spi-max-frequency = <104000000>; spi-tx-bus-width = <4>; spi-rx-bus-width = <4>; }; -
使用SFDP探测:
现代SPI Flash支持Serial Flash Discoverable Parameters,可自动检测芯片参数:c复制static int my_flash_probe(struct spi_mem *spimem) { struct spi_nor *nor; nor = spi_nor_probe(spimem, &spi_nor_sfdp_manufacturers); // ... } -
DMA传输配置:
对于大容量数据传输,启用DMA可显著提升性能。在RK平台需确保:- 内核配置启用CONFIG_SPI_ROCKCHIP_DMA
- 设备树中指定dmas属性:
dts复制&spi0 { dmas = <&dmac0 10>, <&dmac0 11>; dma-names = "tx", "rx"; };
5. 调试与性能优化
5.1 常用调试手段
-
SPI信号质量检测:
- 使用逻辑分析仪抓取CLK/MOSI/MISO信号
- 检查信号过冲、振铃等完整性问题
- 测量实际通信速率是否符合预期
-
内核调试工具:
- SPI层tracepoint:
bash复制echo 1 > /sys/kernel/debug/tracing/events/spi/enable cat /sys/kernel/debug/tracing/trace_pipe - 动态打印调试:
c复制dev_dbg(&spi->dev, "Transfer: len=%d, speed=%d", xfer->len, xfer->speed_hz);
- SPI层tracepoint:
-
性能测量:
bash复制time dd if=/dev/mtdblock0 of=/dev/null bs=4k count=1000
5.2 常见问题排查
-
通信失败:
- 检查设备树SPI引脚是否与其他功能冲突
- 验证CS信号是否正常激活
- 测量电源电压是否稳定(通常需3.3V±5%)
-
SPI Flash识别错误:
- 确认Flash的JEDEC ID读取正确
- 检查是否需发送EN4B/EX4B指令切换地址模式
- 验证Quad模式使能序列
-
屏幕显示异常:
- 检查初始化序列是否完整发送
- 验证色彩格式(RGB565/BGR666等)
- 测量DC信号时序是否符合要求
6. 高级主题与扩展
6.1 双SPI设备共享总线
当多个SPI设备共享同一总线时,需注意:
- 每个设备必须有独立的CS线
- 设备树中正确设置片选引脚:
dts复制&spi1 { cs-gpios = <&gpio3 RK_PB1 GPIO_ACTIVE_LOW>, <&gpio3 RK_PB2 GPIO_ACTIVE_LOW>; }; - 驱动中实现正确的设备选择逻辑
6.2 用户空间SPI访问
除内核驱动外,用户空间可通过spidev直接访问SPI设备:
dts复制&spi1 {
spidev@0 {
compatible = "rohm,dh2228fv";
reg = <0>;
spi-max-frequency = <1000000>;
};
};
应用层示例:
c复制int fd = open("/dev/spidev1.0", O_RDWR);
ioctl(fd, SPI_IOC_WR_MODE, SPI_MODE_0);
ioctl(fd, SPI_IOC_WR_MAX_SPEED_HZ, &speed);
6.3 低功耗优化
针对电池供电设备:
- 空闲时降低SPI时钟或关闭控制器
- 利用Flash的deep power-down模式
- 动态调整屏幕刷新率
实现示例:
c复制static int my_suspend(struct device *dev)
{
struct spi_device *spi = to_spi_device(dev);
/* 进入低功耗模式 */
ili9341_write_cmd(spi, 0x10); // SLEEP_IN
msleep(5);
/* 关闭SPI控制器时钟 */
pinctrl_pm_select_sleep_state(&spi->dev);
return 0;
}
在实际项目中,SPI设备的稳定性和性能往往取决于硬件设计与软件优化的协同。建议在PCB设计阶段就注意SPI走线长度匹配、阻抗控制和干扰隔离,这些硬件因素会直接影响驱动实现的复杂度和最终性能表现。