3. Sensor 驱动与初始化
Sensor 驱动是 ISP Pipeline 的起点。驱动负责完成 Sensor 上电、时钟配置、寄存器初始化、分辨率/帧率切换等关键操作。本章深入分析 Sensor 驱动模型、I2C/SPI 通信、寄存器读写机制、分辨率/帧率设置以及 PLL 配置,帮助学员掌握从驱动加载到图像数据流产生的完整链路。
3.1 Sensor 驱动模型
在 Linux 内核中,Sensor 驱动通常基于 V4L2(Video for Linux 2)子设备框架实现。驱动模型分为三层:
| 层级 | 组件 | 职责 |
|---|---|---|
| 应用层 | 用户空间程序(如 libcamera、gstreamer) | 通过 V4L2 接口打开设备、设置格式、启动/停止流 |
| 核心层 | V4L2 子设备框架 + Media Controller | 管理设备注册、pipeline 路由、事件通知 |
| 硬件层 | Sensor 驱动(I2C/SPI 客户端) | 直接操作 Sensor 寄存器,实现初始化、曝光、增益、帧率控制 |
关键点:Sensor 驱动必须实现
v4l2_subdev_ops 中的 core.s_power、video.s_stream、pad.set_fmt 等回调函数,才能被上层 Pipeline 正确调用。
3.2 I2C/SPI 配置
Sensor 与主控 SoC 之间通常通过 I2C 或 SPI 总线进行寄存器读写。两种总线的选择取决于 Sensor 的寄存器空间大小、速度要求和引脚数量。
| 特性 | I2C | SPI |
|---|---|---|
| 引脚数 | 2(SDA + SCL) | 4(MOSI + MISO + SCLK + CS) |
| 速度 | 标准 100kHz / 快速 400kHz / 高速 3.4MHz | 通常 10MHz ~ 50MHz |
| 寻址方式 | 7位或10位设备地址 + 寄存器地址 | 片选 + 寄存器地址(通常 16位) |
| 适用场景 | 寄存器较少、对引脚敏感的中低端 Sensor | 寄存器较多、需要快速配置的高端 Sensor |
实战建议:在设备树中配置 I2C/SPI 总线频率时,务必参考 Sensor 数据手册的最大支持频率。过高的频率可能导致通信不稳定,尤其在长走线或高噪声环境中。
3.2.1 设备树配置示例(I2C)
/* imx219 Sensor 设备树节点 */
&i2c2 {
status = "okay";
clock-frequency = <400000>; /* 400kHz 快速模式 */
imx219: camera@10 {
compatible = "sony,imx219";
reg = <0x10>; /* I2C 7位地址 */
clocks = <&clks IMX219_CLK>;
clock-names = "xvclk";
clock-frequency = <24000000>; /* 24MHz 主时钟 */
reset-gpios = <&gpio1 15 GPIO_ACTIVE_LOW>;
powerdown-gpios = <&gpio1 16 GPIO_ACTIVE_HIGH>;
port {
imx219_ep: endpoint {
remote-endpoint = <&csi_ep>;
data-lanes = <1 2>;
link-frequencies = /bits/ 64 <456000000>;
};
};
};
};
3.2.2 SPI 配置示例
/* ov5640 Sensor 使用 SPI 接口 */
&spi1 {
status = "okay";
cs-gpios = <&gpio3 20 GPIO_ACTIVE_LOW>;
ov5640: camera@0 {
compatible = "ovti,ov5640";
reg = <0>; /* SPI 片选索引 */
spi-max-frequency = <20000000>; /* 20MHz */
clocks = <&clks OV5640_CLK>;
clock-names = "xclk";
clock-frequency = <24000000>;
reset-gpios = <&gpio4 5 GPIO_ACTIVE_LOW>;
powerdown-gpios = <&gpio4 6 GPIO_ACTIVE_HIGH>;
port {
ov5640_ep: endpoint {
remote-endpoint = <&csi_ep>;
data-lanes = <1 2 3 4>;
link-frequencies = /bits/ 64 <480000000>;
};
};
};
};
3.3 寄存器读写
Sensor 驱动的核心操作是寄存器读写。Linux 内核提供了 i2c_smbus_read_word_data、i2c_smbus_write_word_data 等辅助函数,但更常见的做法是使用 regmap 框架统一管理寄存器访问。
推荐做法:使用
regmap 封装 I2C/SPI 读写,可以自动处理缓存、字节序、多字节传输等问题,同时方便调试(通过 debugfs 导出寄存器值)。
/* 使用 regmap 实现寄存器读写 */
static const struct regmap_config sensor_regmap_config = {
.reg_bits = 16, /* 寄存器地址 16位 */
.val_bits = 8, /* 寄存器值 8位 */
.max_register = 0xffff,
.cache_type = REGCACHE_RBTREE,
};
static int sensor_probe(struct i2c_client *client)
{
struct regmap *regmap;
regmap = devm_regmap_init_i2c(client, &sensor_regmap_config);
if (IS_ERR(regmap))
return PTR_ERR(regmap);
/* 通过 regmap 读写寄存器 */
regmap_write(regmap, 0x0103, 0x01); /* 软件复位 */
msleep(10);
regmap_read(regmap, 0x0000, &chip_id);
dev_info(&client->dev, "Sensor chip ID: 0x%04x\n", chip_id);
return 0;
}
3.3.1 寄存器读写时序
3.4 分辨率/帧率设置
分辨率与帧率的设置本质上是修改 Sensor 内部的行/帧长度寄存器、时钟分频系数以及 PLL 倍频参数。驱动通过预定义的配置表(sensor_mode)来切换不同的工作模式。
3.4.1 配置表结构
/* Sensor 模式配置表 */
struct sensor_mode {
u32 width;
u32 height;
u32 fps; /* 目标帧率,单位:fps */
u32 hts; /* 水平总长度(包括 blanking) */
u32 vts; /* 垂直总长度(包括 blanking) */
u32 pll_mult; /* PLL 倍频系数 */
u32 pre_div; /* PLL 预分频 */
const struct regval_list *regs; /* 寄存器配置序列 */
int num_regs;
};
static const struct sensor_mode sensor_modes[] = {
{
.width = 1920,
.height = 1080,
.fps = 30,
.hts = 2200,
.vts = 1125,
.pll_mult = 48,
.pre_div = 2,
.regs = mode_1920x1080_30fps_regs,
.num_regs = ARRAY_SIZE(mode_1920x1080_30fps_regs),
},
{
.width = 1280,
.height = 720,
.fps = 60,
.hts = 1650,
.vts = 750,
.pll_mult = 36,
.pre_div = 1,
.regs = mode_1280x720_60fps_regs,
.num_regs = ARRAY_SIZE(mode_1280x720_60fps_regs),
},
};
3.4.2 帧率计算公式
帧率由 Sensor 内部时钟频率、水平总长度(HTS)和垂直总长度(VTS)共同决定:
/* 帧率计算公式 */
pixel_clock = input_clock * pll_mult / pre_div;
frame_rate = pixel_clock / (hts * vts);
/* 示例:24MHz 输入时钟,PLL 倍频 48,预分频 2 */
pixel_clock = 24MHz * 48 / 2 = 576MHz;
frame_rate = 576MHz / (2200 * 1125) ≈ 232.7fps; /* 实际需考虑 MIPI 数据速率限制 */
注意:实际帧率还受到 MIPI CSI 带宽、ISP 处理能力、DDR 带宽等多方面限制。驱动中设置的帧率是 Sensor 内部的理论最大值,最终输出帧率可能因下游瓶颈而降低。
3.5 PLL 配置
Sensor 内部的 PLL(锁相环)用于将外部输入时钟倍频到 Sensor 内部所需的工作频率。PLL 配置直接影响像素时钟、MIPI 数据速率以及帧率。
3.5.1 PLL 典型结构
3.5.2 PLL 配置代码示例
/* Sensor PLL 配置函数 */
static int sensor_set_pll(struct regmap *regmap,
u32 input_clk, u32 target_pixel_clk)
{
u32 pre_div, mult, vco_freq;
int ret;
/* 1. 选择预分频,使 VCO 频率落在合理范围 */
for (pre_div = 1; pre_div <= 8; pre_div++) {
vco_freq = target_pixel_clk * pre_div;
if (vco_freq >= 400000000 && vco_freq <= 800000000)
break;
}
/* 2. 计算倍频系数 */
mult = vco_freq / (input_clk / pre_div);
/* 3. 写入 PLL 寄存器 */
ret = regmap_write(regmap, 0x0300, pre_div); /* 预分频寄存器 */
if (ret)
return ret;
ret = regmap_write(regmap, 0x0302, mult); /* 倍频系数寄存器 */
if (ret)
return ret;
/* 4. 等待 PLL 锁定 */
msleep(20);
/* 5. 检查 PLL 锁定状态 */
ret = regmap_read(regmap, 0x0303, &status);
if (ret)
return ret;
if (!(status & 0x01)) {
dev_err(regmap_get_device(regmap), "PLL lock failed\n");
return -EIO;
}
return 0;
}
3.5.3 常见 PLL 配置参数对比
| Sensor 型号 | 输入时钟 | 预分频 | 倍频系数 | VCO 频率 | 像素时钟 | MIPI 速率 |
|---|---|---|---|---|---|---|
| IMX219 | 24MHz | 2 | 48 | 576MHz | 144MHz | 456Mbps/lane |
| OV5640 | 24MHz | 1 | 40 | 960MHz | 120MHz | 480Mbps/lane |
| IMX290 | 37.125MHz | 3 | 54 | 668.25MHz | 148.5MHz | 594Mbps/lane |
调优技巧:PLL 配置时,优先保证 VCO 频率在 Sensor 数据手册推荐的范围内(通常 400MHz~800MHz)。VCO 频率过高会导致功耗增加和抖动恶化,过低则无法锁定或输出时钟质量差。
3.6 初始化流程总结
Sensor 驱动的完整初始化流程如下:
- 设备树解析:获取时钟频率、GPIO、I2C/SPI 总线参数。
- 硬件复位:通过 GPIO 拉低复位引脚,保持至少 1ms 后释放。
- 时钟使能:启用外部时钟(xvclk),等待时钟稳定(通常 5ms)。
- I2C/SPI 通信验证:读取 Sensor 的 Chip ID 寄存器,确认通信正常。
- PLL 配置:根据目标分辨率和帧率计算并写入 PLL 参数。
- 寄存器初始化序列:写入预定义的寄存器配置表(包括模拟电路、数字电路、输出格式等)。
- 模式设置:设置分辨率、帧率、输出格式(RAW8/RAW10/RAW12)。
- 启动流:通过
s_stream(1)回调使能 Sensor 输出 MIPI 数据。