第3章:Sensor驱动移植——I2C通信调试、寄存器配置与probe流程
Sensor驱动移植,说白了就是让主控芯片能跟Sensor“说上话”。我做了这么多年,发现很多新手上来就调图像,结果I2C都没通,折腾半天白费劲。这一章咱们就把底层的通信、配置、上电时序这些基本功打扎实。
3.1 I2C通信调试——打通“第一公里”
Sensor跟主控之间最常见的通信方式就是I2C。RK3588、RK3568这些芯片的I2C控制器很成熟,但硬件连接上的坑可不少。
3.1.1 硬件连接检查
拿到一块Sensor模组,我第一件事不是写代码,而是拿万用表量三样东西:
- SCL/SDA上拉电阻:一般1.5kΩ~4.7kΩ,看总线负载。没上拉?波形肯定不对。
- 电平匹配:Sensor的IO电压是1.8V还是3.3V?RK芯片的I2C引脚电压要跟它一致。我遇到过Sensor是1.8V,RK那边配了3.3V,结果烧了Sensor的IO口——嗯,那次挺惨的。
- 地址冲突:同一条I2C总线上挂了多个设备?检查地址有没有重复。
3.1.2 用i2c-tools快速验证
别急着写驱动。先进系统用i2c-tools扫一下,确认设备在不在:
# 扫描I2C总线,查看所有设备
i2cdetect -y 0 # 0号总线
i2cdetect -y 1 # 1号总线
# 如果看到0x30、0x36之类的地址,说明Sensor应答了
# 直接读Sensor的ID寄存器验证
i2cget -y 1 0x30 0x0f w # 读0x30设备的0x0f寄存器
如果i2cdetect扫不到地址,先别怀疑代码。我一般按这个顺序排查:
- 万用表量SCL/SDA电压——是不是被拉死了?
- 示波器看波形——幅值够不够?时序对不对?
- 检查Sensor的reset引脚——是不是被拉在复位状态?
- 确认电源——Sensor的AVDD、DVDD、IOVDD都上电了吗?
⚠️ 我曾经踩过的坑:Sensor的IOVDD是1.8V,但RK的I2C引脚配置成了3.3V。i2cdetect能扫到地址,但读写寄存器总是返回0xFF。折腾了两天才发现是电平不匹配,加了个电平转换芯片搞定。
3.2 Sensor寄存器配置——读懂datasheet是关键
每个Sensor都有自己的寄存器地图。移植驱动,说白了就是把datasheet里的初始化序列翻译成代码。
3.2.1 寄存器读写封装
RK平台下,Sensor驱动通常用regmap接口。我习惯这样封装:
static int sensor_read_reg(struct i2c_client *client, u16 reg, u8 *val)
{
struct i2c_msg msg[2];
int ret;
msg[0].addr = client->addr;
msg[0].flags = 0;
msg[0].len = 2;
msg[0].buf = (u8 *)®
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) {
dev_err(&client->dev, "read reg 0x%04x failed\n", reg);
return -EIO;
}
return 0;
}
static int sensor_write_reg(struct i2c_client *client, u16 reg, u8 val)
{
u8 buf[3];
struct i2c_msg msg;
int ret;
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) {
dev_err(&client->dev, "write reg 0x%04x failed\n", reg);
return -EIO;
}
return 0;
}
注意:有些Sensor的寄存器地址是8位,有些是16位。写代码前一定看清楚datasheet里的地址宽度。
3.2.2 初始化序列的组织方式
Sensor的初始化寄存器配置少则几十行,多则几百行。我推荐用结构体数组来组织:
struct sensor_regval {
u16 reg;
u8 val;
};
static const struct sensor_regval sensor_init_regs[] = {
{0x0103, 0x01}, // 软件复位
{0x0100, 0x00}, // 进入待机模式
{0x0305, 0x01}, // PLL配置
{0x0307, 0x5C},
{0x030B, 0x01},
// ... 几百行配置
{0x0100, 0x01}, // 启动streaming
};
static int sensor_load_regs(struct i2c_client *client,
const struct sensor_regval *regs, int len)
{
int i, ret;
for (i = 0; i < len; i++) {
ret = sensor_write_reg(client, regs[i].reg, regs[i].val);
if (ret)
return ret;
// 有些寄存器写入后需要等待
if (regs[i].reg == 0x0103)
msleep(10);
}
return 0;
}
💡 我的习惯:把datasheet里的寄存器表直接转成C数组,用Excel或者Python脚本批量生成。手动一个一个敲?太容易出错了,我吃过这个亏。
3.3 驱动probe流程——从设备树到设备注册
probe函数是驱动的入口。RK平台下,Sensor驱动的probe流程大致是这样的:
3.3.1 设备树配置
先看设备树怎么写。以RK3588为例:
&i2c4 {
status = "okay";
pinctrl-names = "default";
pinctrl-0 = <&i2c4m2_xfer>;
sensor0: sensor@30 {
compatible = "sony,imx415";
reg = <0x30>;
clocks = <&cru CLK_MIPI_CAM0>;
clock-names = "xvclk";
pinctrl-names = "default";
pinctrl-0 = <&sensor0_pins>;
reset-gpios = <&gpio1 RK_PB0 GPIO_ACTIVE_LOW>;
powerdown-gpios = <&gpio1 RK_PB1 GPIO_ACTIVE_HIGH>;
avdd-supply = <&vcc_avdd_sensor>;
dvdd-supply = <&vcc_dvdd_sensor>;
iovdd-supply = <&vcc_iovdd_sensor>;
rockchip,camera-module-index = <0>;
rockchip,camera-module-facing = "back";
};
};
这里有几个关键点:
- compatible:跟驱动里的of_match_table对应
- reg:I2C地址,必须跟Sensor硬件一致
- reset-gpios/powerdown-gpios:控制引脚,注意极性
- supply:电源 regulator,驱动里会通过regulator框架控制
3.3.2 probe函数核心流程
probe函数里我一般按这个顺序来:
- 解析设备树:获取GPIO、时钟、电源等资源
- 上电:调用power on序列
- 复位Sensor:拉低reset引脚再释放
- 读取Sensor ID:验证通信是否正常
- 加载初始化序列:写入寄存器配置
- 注册V4L2子设备:让上层框架能调用
- 注册media entity:建立pipeline
static int sensor_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
struct device *dev = &client->dev;
struct sensor *sensor;
int ret;
sensor = devm_kzalloc(dev, sizeof(*sensor), GFP_KERNEL);
if (!sensor)
return -ENOMEM;
sensor->client = client;
i2c_set_clientdata(client, sensor);
// 1. 解析设备树资源
ret = sensor_parse_dt(sensor);
if (ret)
return ret;
// 2. 上电
ret = sensor_power_on(sensor);
if (ret) {
dev_err(dev, "power on failed\n");
return ret;
}
// 3. 复位
gpiod_set_value_cansleep(sensor->reset_gpio, 0);
usleep_range(1000, 2000);
gpiod_set_value_cansleep(sensor->reset_gpio, 1);
usleep_range(10000, 15000);
// 4. 读取Sensor ID
ret = sensor_read_reg(client, SENSOR_CHIP_ID_REG, &chip_id);
if (ret || chip_id != SENSOR_CHIP_ID) {
dev_err(dev, "chip id mismatch: 0x%02x\n", chip_id);
return -ENODEV;
}
// 5. 加载初始化序列
ret = sensor_load_regs(client, sensor_init_regs,
ARRAY_SIZE(sensor_init_regs));
if (ret)
return ret;
// 6. 注册V4L2子设备
ret = sensor_register_subdev(sensor);
if (ret)
return ret;
dev_info(dev, "sensor probe success\n");
return 0;
}
🔑 关键点:probe失败时一定要调用power off清理资源。我见过驱动probe到一半失败了,结果Sensor一直处于上电状态,功耗下不来。
3.4 Power On/Off序列——时序不对,图像全废
Sensor的上电时序非常敏感。AVDD、DVDD、IOVDD、MCLK、reset、powerdown这几个信号的先后顺序和延时,datasheet里都有明确要求。
3.4.1 典型的上电时序
以IMX415为例,上电顺序一般是:
| 步骤 | 操作 | 延时 | 说明 |
|---|---|---|---|
| 1 | 拉高IOVDD (1.8V) | 0ms | 先给IO供电 |
| 2 | 拉高AVDD (2.9V) | ≥1ms | 模拟电源 |
| 3 | 拉高DVDD (1.2V) | ≥1ms | 数字核心电源 |
| 4 | 提供MCLK时钟 | ≥1ms | 时钟稳定后再复位 |
| 5 | 释放reset(拉高) | ≥10ms | 复位结束 |
| 6 | 拉低powerdown | ≥5ms | 退出待机模式 |
| 7 | I2C通信开始 | ≥20ms | 等待内部初始化完成 |
3.4.2 代码实现
static int sensor_power_on(struct sensor *sensor)
{
struct device *dev = &sensor->client->dev;
int ret;
// 1. IOVDD先上
ret = regulator_enable(sensor->iovdd_supply);
if (ret) return ret;
usleep_range(1000, 2000);
// 2. AVDD
ret = regulator_enable(sensor->avdd_supply);
if (ret) goto err_avdd;
usleep_range(1000, 2000);
// 3. DVDD
ret = regulator_enable(sensor->dvdd_supply);
if (ret) goto err_dvdd;
usleep_range(1000, 2000);
// 4. 提供MCLK
ret = clk_prepare_enable(sensor->xvclk);
if (ret) goto err_clk;
usleep_range(1000, 2000);
// 5. 释放reset
gpiod_set_value_cansleep(sensor->reset_gpio, 1);
usleep_range(10000, 15000);
// 6. 拉低powerdown
gpiod_set_value_cansleep(sensor->powerdown_gpio, 0);
usleep_range(5000, 10000);
return 0;
err_clk:
regulator_disable(sensor->dvdd_supply);
err_dvdd:
regulator_disable(sensor->avdd_supply);
err_avdd:
regulator_disable(sensor->iovdd_supply);
return ret;
}
static int sensor_power_off(struct sensor *sensor)
{
// 关电顺序跟上电相反
gpiod_set_value_cansleep(sensor->powerdown_gpio, 1);
gpiod_set_value_cansleep(sensor->reset_gpio, 0);
clk_disable_unprepare(sensor->xvclk);
regulator_disable(sensor->dvdd_supply);
regulator_disable(sensor->avdd_supply);
regulator_disable(sensor->iovdd_supply);
return 0;
}
⚠️ 我曾经踩过的坑:有个项目Sensor的powerdown引脚默认是浮空,内部下拉。我上电后没主动拉低powerdown,结果Sensor一直处于待机模式,I2C能读到ID,但就是不输出图像。查了两天才发现是powerdown没拉低。
3.5 知识体系总览
下面这张图把Sensor驱动移植的核心流程串起来了:
3.6 常见问题与避坑指南
- I2C通信不稳定:检查上拉电阻、总线电容、时钟频率。RK的I2C默认100kHz,有些Sensor需要降到50kHz才能稳定。
- 读ID正确但图像异常:大概率是初始化序列漏了某条寄存器配置。我习惯把datasheet里的初始化表逐条跟代码比对。
- 上电后Sensor发热:检查电源电压是否超标,或者power down引脚没拉低导致Sensor处于active模式。
- probe成功但无法streaming:检查MCLK时钟是否稳定,用示波器看波形。RK的CLK输出可能默认是关闭的,需要在设备树里配置。
💡 我的调试习惯:在probe函数里加几个dev_info打印,把关键步骤的执行结果打出来。上电后读到的ID、初始化序列加载了多少条、注册子设备是否成功——这些信息对定位问题非常有帮助。