4、摄像头驱动初始化:probe函数实现、时钟与电源管理、GPIO与复位时序
好,咱们今天聊点硬核的。摄像头驱动能不能跑起来,全看probe函数这一哆嗦。我见过太多新手,在设备树里配得漂漂亮亮,结果probe函数写成一团浆糊,摄像头死活不干活。说白了,probe就是驱动和硬件的「第一次握手」,握不好,后面全白搭。
4.1 probe函数:驱动的入场券
probe函数什么时候被调用?当内核发现设备树里的节点和驱动里的compatible匹配上了,就会调用你的probe。嗯,这里要注意,匹配成功不代表万事大吉,你还要做一堆初始化工作。
我个人习惯,probe函数里按这个顺序来:
- 获取设备树资源——reg、中断、GPIO、时钟,一个都不能少
- 时钟使能——不给摄像头喂时钟,它就是个哑巴
- 电源上电——电压不对,芯片直接冒烟
- GPIO复位时序——这个最坑,时序不对摄像头不认你
- 初始化i2c通信——读写传感器寄存器
- 注册v4l2设备——让上层应用能看到你
来看一个精简的probe框架:
static int ov5640_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
struct ov5640_dev *sensor;
int ret;
// 1. 分配设备结构体
sensor = devm_kzalloc(&client->dev, sizeof(*sensor), GFP_KERNEL);
if (!sensor)
return -ENOMEM;
// 2. 获取时钟
sensor->xvclk = devm_clk_get(&client->dev, "xvclk");
if (IS_ERR(sensor->xvclk))
return PTR_ERR(sensor->xvclk);
// 3. 获取复位GPIO
sensor->reset_gpio = devm_gpiod_get(&client->dev, "reset", GPIOD_OUT_HIGH);
if (IS_ERR(sensor->reset_gpio))
return PTR_ERR(sensor->reset_gpio);
// 4. 获取电源
sensor->avdd = devm_regulator_get(&client->dev, "avdd");
sensor->dvdd = devm_regulator_get(&client->dev, "dvdd");
// 5. 上电复位时序
ret = ov5640_power_on(sensor);
if (ret)
return ret;
// 6. 读取芯片ID确认通信
ret = ov5640_check_chip_id(sensor);
if (ret)
goto power_off;
// 7. 注册v4l2子设备
ret = v4l2_i2c_subdev_init(&sensor->sd, client, &ov5640_subdev_ops);
if (ret)
goto power_off;
return 0;
power_off:
ov5640_power_off(sensor);
return ret;
}
你看,每一步都有对应的错误处理。我在项目中遇到过有人probe函数写了200行,一个错误处理都没有,结果摄像头一上电就挂,查了三天发现是电源没使能。所以,probe函数里每个资源申请都要检查返回值,这是底线。
4.2 时钟与电源管理:别让摄像头饿着
摄像头是个耗电大户,尤其是VGA以上的传感器。时钟和电源管理做不好,轻则图像花屏,重则芯片烧毁。
4.2.1 时钟配置
大部分摄像头需要外部提供主时钟(MCLK/XCLK),频率通常是24MHz、27MHz或48MHz。你想想看,时钟频率不对,内部PLL锁不住,像素时钟就乱套了。
我建议在设备树里这样配:
ov5640: camera@3c {
compatible = "ovti,ov5640";
reg = <0x3c>;
clocks = <&clks IMX6QDL_CLK_CKO>;
clock-names = "xvclk";
assigned-clocks = <&clks IMX6QDL_CLK_CKO>;
assigned-clock-rates = <24000000>;
...
};
驱动里使能时钟的代码:
static int ov5640_set_clock(struct ov5640_dev *sensor)
{
int ret;
ret = clk_set_rate(sensor->xvclk, 24000000);
if (ret < 0)
return ret;
ret = clk_prepare_enable(sensor->xvclk);
if (ret)
return ret;
// 等时钟稳定,至少1ms
usleep_range(1000, 2000);
return 0;
}
这里有个坑:clk_prepare_enable可能会睡眠,所以不能在原子上下文调用。probe函数里没问题,但如果你在中断里调,系统就崩了。我曾经在中断里调了clk_prepare_enable,结果内核直接panic,教训深刻。
4.2.2 电源管理
摄像头一般需要三路电源:
| 电源名称 | 典型电压 | 用途 |
|---|---|---|
| AVDD | 2.8V | 模拟电路供电 |
| DVDD | 1.5V / 1.8V | 数字核心供电 |
| IOVDD | 1.8V / 2.8V | IO接口供电 |
上电顺序很重要。大部分传感器要求:先上AVDD,再上DVDD,最后上IOVDD。顺序反了,芯片内部闩锁效应可能直接烧掉。
看一个规范的上电函数:
static int ov5640_power_on(struct ov5640_dev *sensor)
{
int ret;
// 1. 先使能AVDD 2.8V
ret = regulator_enable(sensor->avdd);
if (ret)
return ret;
usleep_range(1000, 2000); // 等1ms
// 2. 再使能DVDD 1.5V
ret = regulator_enable(sensor->dvdd);
if (ret)
goto disable_avdd;
usleep_range(1000, 2000);
// 3. 最后使能IOVDD 1.8V
ret = regulator_enable(sensor->iovdd);
if (ret)
goto disable_dvdd;
usleep_range(5000, 10000); // 等5ms让电源稳定
// 4. 使能时钟
ret = ov5640_set_clock(sensor);
if (ret)
goto disable_iovdd;
return 0;
disable_iovdd:
regulator_disable(sensor->iovdd);
disable_dvdd:
regulator_disable(sensor->dvdd);
disable_avdd:
regulator_disable(sensor->avdd);
return ret;
}
你可能会问:为什么每个电源之间要等1ms?因为电源芯片的启动需要时间,电压爬坡不是瞬间完成的。我见过有人把延时去掉,结果摄像头10次里有3次初始化失败,加上延时后一次都没出过问题。
4.3 GPIO与复位时序:最容易被忽视的细节
好,终于到了最坑的部分——复位时序。说白了,摄像头芯片内部有个状态机,上电后需要给它一个特定的复位信号,它才能进入正常工作模式。
典型的复位时序是这样的:
- 上电完成后,保持复位引脚为低电平(复位状态)
- 等待至少T1时间(通常1ms~10ms)
- 将复位引脚拉高(释放复位)
- 等待至少T2时间(通常5ms~20ms),让内部PLL锁定
- 然后才能通过I2C访问传感器寄存器
来看代码实现:
static int ov5640_reset(struct ov5640_dev *sensor)
{
// 1. 拉低复位引脚,进入复位状态
gpiod_set_value_cansleep(sensor->reset_gpio, 0);
usleep_range(1000, 2000); // T1: 至少1ms
// 2. 拉高复位引脚,释放复位
gpiod_set_value_cansleep(sensor->reset_gpio, 1);
usleep_range(5000, 10000); // T2: 等待5ms
// 3. 再拉低一次,有些传感器需要脉冲复位
gpiod_set_value_cansleep(sensor->reset_gpio, 0);
usleep_range(1000, 2000);
// 4. 最终拉高,完成复位
gpiod_set_value_cansleep(sensor->reset_gpio, 1);
usleep_range(20000, 30000); // 等20ms让内部初始化完成
return 0;
}
另外,GPIO的操作要注意:
- 使用devm_gpiod_get获取GPIO,这样驱动卸载时会自动释放
- GPIO的极性在设备树里配,比如GPIO_ACTIVE_LOW,驱动里统一用1表示有效
- 复位操作要放在电源使能之后,顺序不能乱
4.4 完整的初始化流程
把上面这些串起来,一个完整的初始化流程应该是:
static int ov5640_initialize(struct ov5640_dev *sensor)
{
int ret;
// 1. 时钟使能
ret = ov5640_set_clock(sensor);
if (ret) {
dev_err(&sensor->client->dev, "failed to set clock\n");
return ret;
}
// 2. 电源上电(含顺序控制)
ret = ov5640_power_on(sensor);
if (ret) {
dev_err(&sensor->client->dev, "failed to power on\n");
goto disable_clk;
}
// 3. 复位时序
ret = ov5640_reset(sensor);
if (ret) {
dev_err(&sensor->client->dev, "failed to reset\n");
goto power_off;
}
// 4. 等待传感器稳定
msleep(30);
// 5. 读取芯片ID验证通信
ret = ov5640_read_reg(sensor, REG_CHIP_ID_HIGH, &id_high);
if (ret || id_high != 0x56) {
dev_err(&sensor->client->dev, "chip ID mismatch\n");
goto power_off;
}
// 6. 加载初始化寄存器序列
ret = ov5640_load_init_seq(sensor);
if (ret) {
dev_err(&sensor->client->dev, "failed to load init seq\n");
goto power_off;
}
return 0;
power_off:
ov5640_power_off(sensor);
disable_clk:
clk_disable_unprepare(sensor->xvclk);
return ret;
}
嗯,到这里,probe函数的核心逻辑就完整了。你想想看,从设备树匹配到时钟使能,从电源上电到复位时序,每一步都环环相扣。哪个环节出问题,摄像头都起不来。
最后说一句:调试摄像头驱动,示波器和逻辑分析仪是必备的。别光靠printk,你看不到时钟波形,看不到复位时序,看不到I2C通信,很多问题根本定位不了。我刚开始做驱动时,全靠printk打日志,一个复位时序问题查了一周。后来买了台二手示波器,半天就搞定了。
下一章咱们聊I2C通信和传感器寄存器配置,那又是另一片天地了。