驱动初始化流程:设备树解析、电源上电序列、时钟配置、复位与初始化序列、ID读取验证
各位做Camera驱动的朋友,大家好。今天我们来聊聊驱动初始化流程。说实话,这部分是驱动稳定性的基石。很多莫名其妙的bug,追根溯源,都是初始化阶段埋下的坑。
我个人习惯把初始化拆成五个关键步骤:设备树解析、电源上电、时钟配置、复位与初始化、ID验证。每一步都有讲究,我们一个一个来看。
1. 设备树解析:你的硬件到底长什么样?
设备树(DT)是驱动和硬件之间的“翻译官”。驱动通过它知道:Sensor挂在哪个I2C总线上?用哪路MCLK?电源怎么控?
我建议你养成一个习惯:所有硬件参数都从设备树读,不要硬编码。为什么?因为同一个驱动可能要适配多个项目,硬编码会让你改到崩溃。
举个例子,一个典型的Camera Sensor设备树节点长这样:
sensor0: camera-sensor@10 {
compatible = "sony,imx307";
reg = <0x10>;
clocks = <&cam_clk 0>;
clock-names = "xvclk";
clock-frequency = <24000000>;
avdd-supply = <&cam_avdd>;
iovdd-supply = <&cam_iovdd>;
dvdd-supply = <&cam_dvdd>;
reset-gpios = <&gpio1 15 GPIO_ACTIVE_LOW>;
powerdown-gpios = <&gpio1 16 GPIO_ACTIVE_HIGH>;
status = "okay";
};
解析时,我一般按这个顺序来:
- 获取基础资源:I2C地址、中断号、GPIO
- 获取时钟信息:时钟源、频率
- 获取电源信息:各路供电的regulator
- 获取私有属性:比如lane数、翻转方向等
2. 电源上电序列:顺序错了,Sensor就挂了
电源上电,说白了就是给Sensor“喂电”。但喂电的顺序和时间间隔,每个Sensor都有自己的脾气。
我见过最典型的坑:先上IO电压,再上模拟电压。有些Sensor要求先上模拟电压再上IO,顺序反了,轻则初始化失败,重则烧坏芯片。
一般上电序列是这样的:
- 使能主电源(AVDD、DVDD)
- 等待稳定(通常1-5ms)
- 使能IO电源(IOVDD)
- 等待稳定(1ms左右)
- 拉高复位引脚(释放复位)
- 等待Sensor内部初始化完成(通常10-20ms)
代码实现上,我习惯用regulator框架:
static int sensor_power_on(struct sensor_dev *sdev)
{
int ret;
// 先上模拟电源
ret = regulator_enable(sdev->avdd);
if (ret) goto err_avdd;
usleep_range(2000, 3000);
// 再上IO电源
ret = regulator_enable(sdev->iovdd);
if (ret) goto err_iovdd;
usleep_range(1000, 2000);
// 释放复位
gpiod_set_value(sdev->reset_gpio, 1);
usleep_range(10000, 15000);
return 0;
err_iovdd:
regulator_disable(sdev->avdd);
err_avdd:
return ret;
}
3. 时钟配置:频率不准,图像就花
时钟是Sensor的“心跳”。MCLK频率不对,PLL就锁不住,出来的图像要么花屏,要么帧率不对。
我建议你这样做:
- 从设备树读取目标频率
- 调用clk_set_rate设置时钟
- 用clk_get_rate确认实际频率
为什么还要确认?因为有些时钟源可能不支持你想要的频率,它会自动选一个最接近的。你不检查,就埋下了隐患。
unsigned long target_rate = 24000000; // 24MHz
unsigned long actual_rate;
clk_set_rate(sdev->xvclk, target_rate);
actual_rate = clk_get_rate(sdev->xvclk);
if (abs(actual_rate - target_rate) > 100000) {
dev_warn(sdev->dev, "MCLK频率偏差过大: %lu vs %lu\n",
actual_rate, target_rate);
}
4. 复位与初始化序列:让Sensor“醒过来”
复位和初始化,说白了就是让Sensor从“沉睡”状态进入“工作”状态。这个过程,每个Sensor都有自己的“暗号”。
一般流程是:
- 拉低复位引脚(进入复位状态)
- 等待至少1ms
- 拉高复位引脚(释放复位)
- 等待Sensor内部初始化(通常10-30ms)
- 通过I2C写入初始化寄存器序列
初始化寄存器序列,我建议你这样做:
- 先写软件复位寄存器(让Sensor内部复位)
- 等待复位完成(轮询状态寄存器)
- 配置模式寄存器(分辨率、帧率、输出格式)
- 配置模拟增益、曝光等参数
- 启动输出(设置Stream On)
static int sensor_init_sequence(struct sensor_dev *sdev)
{
int ret;
// 软件复位
ret = sensor_write_reg(sdev, 0x0103, 0x01);
if (ret) return ret;
msleep(10);
// 等待复位完成
ret = sensor_poll_reg(sdev, 0x0100, 0x00, 100);
if (ret) {
dev_err(sdev->dev, "Sensor复位超时\n");
return -ETIMEDOUT;
}
// 配置输出模式
sensor_write_reg(sdev, 0x0340, 0x04); // 高度高字节
sensor_write_reg(sdev, 0x0341, 0x38); // 高度低字节
// ... 更多配置
return 0;
}
5. ID读取验证:确认你找对了“人”
ID验证,是初始化流程的最后一道防线。说白了就是读Sensor的型号寄存器,确认你操作的是正确的设备。
为什么需要这一步?因为I2C总线上可能挂多个设备,或者焊接有问题导致地址冲突。不验证,你后面写的所有寄存器都可能写到了错误的设备上。
一般做法是:
- 读取芯片ID寄存器(通常是0x0000或0x0001)
- 与datasheet中的预期值比较
- 匹配则继续,不匹配则返回错误
static int sensor_check_id(struct sensor_dev *sdev)
{
u16 chip_id;
int ret;
ret = sensor_read_reg(sdev, 0x0000, &chip_id);
if (ret) {
dev_err(sdev->dev, "读取芯片ID失败\n");
return ret;
}
if (chip_id != 0x0307) { // IMX307的ID
dev_err(sdev->dev, "芯片ID不匹配: 0x%04x, 期望0x0307\n",
chip_id);
return -ENODEV;
}
dev_info(sdev->dev, "芯片ID验证通过: 0x%04x\n", chip_id);
return 0;
}
总结一下
驱动初始化流程,说白了就是“对暗号”的过程。设备树告诉驱动硬件长什么样,电源和时钟让Sensor“活过来”,复位和初始化让Sensor“准备好”,ID验证确认“没找错人”。
每一步都马虎不得。我见过太多驱动不稳定,最后发现是初始化阶段某个延时不够,或者某个寄存器没配对。嗯,细节决定成败,这话在驱动开发里一点不假。
下一章,我们会深入聊聊I2C通信的稳定性问题。到时候见。