1. RK平台Sensor驱动框架概述:V4L2子系统、I2C总线、DTS设备树节点配置
在RK平台上开发Sensor驱动,首先需要理解其核心软件架构。Sensor驱动并非孤立存在,而是深度嵌入在Linux内核的V4L2(Video for Linux 2)框架中,通过I2C总线与硬件通信,并由设备树(Device Tree)完成硬件资源的静态描述。本章将拆解这三个核心要素,为后续的寄存器读写与性能优化打下坚实基础。
1.1 V4L2子系统:Sensor驱动的“操作系统”
V4L2是Linux内核中为视频设备(包括Camera Sensor)提供统一接口的框架。它将Sensor抽象为一个“视频设备节点”(通常是/dev/videoX),使得用户空间的应用(如Camera HAL、GStreamer)可以通过标准系统调用(open、ioctl、mmap)来控制Sensor。
在V4L2框架中,Sensor驱动主要扮演“子设备(subdev)”的角色。驱动需要实现一组预定义的回调函数,用于配置分辨率、帧率、曝光、增益等参数。下图展示了V4L2子设备在RK平台Camera软件栈中的位置:
驱动开发者需要重点关注V4L2 Subdev层的实现。核心工作包括:
- 实现
struct v4l2_subdev_ops:提供core、video、pad等操作集。 - 实现
struct v4l2_subdev_video_ops:关键函数如s_stream()(开启/关闭流)、g_frame_interval()(获取帧率)。 - 实现
struct v4l2_subdev_pad_ops:用于设置媒体总线格式(set_fmt)和裁剪(set_selection)。 - 注册 Subdev:通过
v4l2_async_register_subdev()将驱动注册到V4L2异步框架中。
cat /sys/kernel/debug/v4l2/ 查看已注册的V4L2设备列表。使用 media-ctl 工具可以动态查看和修改Pipeline拓扑,是排查Sensor枚举问题的利器。
1.2 I2C总线:寄存器读写的“高速公路”
绝大多数Camera Sensor通过I2C总线与主控SoC(如RK3588、RK3568)通信。I2C是一种同步、多主从、半双工的串行通信协议,使用两根线:SCL(时钟线)和SDA(数据线)。Sensor驱动通过I2C读写Sensor内部的寄存器,以完成初始化、配置工作模式、获取状态等操作。
在Linux内核中,I2C驱动被分为两个层次:
| 层次 | 组件 | 职责 | 开发者关注点 |
|---|---|---|---|
| I2C 总线驱动 | i2c-rk3x.c (RK平台) | 处理I2C控制器硬件,适配器算法,产生时序 | 通常由BSP提供,无需修改;需确认时钟频率、DMA支持 |
| I2C 设备驱动 | sensor.c (你的驱动) | 通过I2C核心API与Sensor芯片通信 | 使用 i2c_transfer() 或 i2c_smbus_* 函数 |
Sensor驱动中最常用的I2C读写函数封装如下:
/* 单字节写寄存器 */
static int sensor_write_reg(struct i2c_client *client, u16 reg, u8 val)
{
int ret;
struct i2c_msg msg;
u8 buf[3];
buf[0] = (reg >> 8) & 0xFF; // 寄存器高字节
buf[1] = reg & 0xFF; // 寄存器低字节
buf[2] = val; // 写入值
msg.addr = client->addr;
msg.flags = 0; // 写标志
msg.len = 3;
msg.buf = buf;
ret = i2c_transfer(client->adapter, &msg, 1);
if (ret == 1)
return 0;
else
return -EIO;
}
/* 多字节读寄存器 */
static int sensor_read_reg(struct i2c_client *client, u16 reg, u8 *val)
{
int ret;
struct i2c_msg msg[2];
u8 reg_buf[2];
reg_buf[0] = (reg >> 8) & 0xFF;
reg_buf[1] = reg & 0xFF;
msg[0].addr = client->addr;
msg[0].flags = 0; // 写寄存器地址
msg[0].len = 2;
msg[0].buf = reg_buf;
msg[1].addr = client->addr;
msg[1].flags = I2C_M_RD; // 读数据
msg[1].len = 1;
msg[1].buf = val;
ret = i2c_transfer(client->adapter, msg, 2);
if (ret == 2)
return 0;
else
return -EIO;
}
I2C通信的时序与速率直接影响Sensor的初始化速度和配置灵活性。下图展示了Sensor驱动通过I2C总线进行寄存器读写的典型流程:
1.3 DTS设备树节点配置:硬件拓扑的“静态地图”
在RK平台(以及所有基于ARM的Linux系统)中,设备树(Device Tree Source, DTS)是描述硬件拓扑和资源分配的核心机制。对于Sensor驱动而言,DTS节点主要完成以下工作:
- 绑定I2C总线:指定Sensor挂载在哪个I2C控制器上。
- 指定I2C地址:Sensor的7位从机地址。
- 配置电源与复位:定义AVDD、DVDD、IOVDD等电源,以及复位GPIO。
- 关联时钟:指定MCLK(主时钟)的时钟源和频率。
- 定义中断:如果Sensor支持中断(如VSYNC),则在此声明。
一个典型的RK3588平台Sensor DTS节点示例如下:
/* 在 i2c8 节点下添加 Sensor */
&i2c8 {
status = "okay";
pinctrl-names = "default";
pinctrl-0 = <&i2c8m2_xfer>;
ov5648: ov5648@36 {
compatible = "ovti,ov5648";
reg = <0x36>; // I2C 从机地址 (7位)
clocks = <&cru CLK_MIPI_CAMARAOUT_M3>;
clock-names = "xvclk";
clock-frequency = <24000000>; // MCLK 频率 24MHz
// 电源配置
avdd-supply = <&vcc_camera_avdd>;
dovdd-supply = <&vcc_camera_dovdd>;
dvdd-supply = <&vcc_camera_dvdd>;
// 复位与电源控制 GPIO
reset-gpios = <&gpio3 RK_PB6 GPIO_ACTIVE_LOW>;
pwdn-gpios = <&gpio3 RK_PB7 GPIO_ACTIVE_HIGH>;
// 端口连接 (绑定到 MIPI CSI 接口)
port {
ov5648_out: endpoint {
remote-endpoint = <&mipi_in_ucam0>;
data-lanes = <1 2>; // 使用 2-lane MIPI
};
};
};
};
DTS节点中的关键属性及其作用如下表所示:
| 属性名 | 类型 | 说明 | 调试要点 |
|---|---|---|---|
compatible |
字符串 | 驱动匹配的关键标识,必须与驱动中 of_match_table 一致 |
拼写错误会导致驱动无法加载 |
reg |
u32 | I2C从机地址(7位左移1位后的值) | 使用 i2cdetect 工具确认实际地址 |
clock-frequency |
u32 | Sensor主时钟频率,单位Hz | 频率偏差过大会导致PLL锁定失败 |
reset-gpios |
GPIO句柄 | 复位引脚,通常为低电平有效 | 检查GPIO复用(pinctrl)是否正确 |
data-lanes |
u32数组 | MIPI CSI数据通道映射 | 必须与硬件连线一致,否则无图像 |
ls /sys/firmware/devicetree/base/ 可以查看实际生效的设备树。使用 dtc -I fs -O dts /sys/firmware/devicetree/base/ 可以反编译当前设备树,验证节点是否被正确解析。
下图展示了DTS节点如何将Sensor驱动与硬件资源关联起来: