4. Sensor驱动初始化流程:probe函数编写、设备树解析、电源管理、v4l2_subdev注册
Sensor驱动的初始化流程是V4L2 Subdev开发的核心环节,它决定了设备能否被内核正确识别、电源能否稳定供给、以及上层应用能否通过Video节点访问图像数据。本章将深入剖析probe函数的完整编写范式,涵盖设备树解析、电源管理(regulator与clock)以及v4l2_subdev注册等关键步骤。
核心要点: Sensor驱动的probe函数是设备生命周期的起点,其执行顺序直接影响后续所有操作。一个健壮的probe流程应遵循“先解析、后使能、再注册”的原则。
4.1 probe函数整体架构
probe函数是驱动与内核设备模型交互的入口。当设备树中描述的Sensor节点与驱动匹配时,内核调用probe函数。其典型执行流程如下:
- 设备树解析: 从设备节点中读取regulator、clock、GPIO、数据接口等配置信息。
- 电源管理初始化: 获取并使能regulator和clock资源,确保Sensor处于工作状态。
- 硬件初始化: 通过I2C/SPI总线写入寄存器序列,完成Sensor内部配置。
- V4L2 Subdev注册: 初始化并注册v4l2_subdev结构体,将其挂载到V4L2框架中。
- 媒体实体与链接: 创建media_entity并定义pad之间的数据流链接。
开发建议: 在probe函数中,每一步操作都应检查返回值,并使用dev_err()打印错误信息。推荐使用
devm_系列API(如devm_regulator_get)简化资源管理。
4.2 设备树解析
设备树(Device Tree)是描述硬件拓扑和资源配置的标准方式。Sensor驱动通过of_match_table匹配设备节点,并使用OF API解析关键属性。
4.2.1 设备树节点示例
// imx219_sensor.dtsi 片段
i2c@ff160000 {
#address-cells = <1>;
#size-cells = <0>;
reg = <0xff160000 0x1000>;
sensor_imx219: camera-sensor@10 {
compatible = "sony,imx219";
reg = <0x10>;
clocks = <&clk_cam 0>;
clock-names = "xvclk";
avdd-supply = <®_cam_avdd>;
dvdd-supply = <®_cam_dvdd>;
dovdd-supply = <®_cam_dovdd>;
reset-gpios = <&gpio2 5 GPIO_ACTIVE_LOW>;
port {
sensor_ep: endpoint {
remote-endpoint = <&csi_ep>;
data-lanes = <1 2>;
link-frequencies = /bits/ 64 <456000000>;
};
};
};
};
4.2.2 驱动中解析设备树的典型代码
static int sensor_parse_dt(struct device *dev, struct sensor_state *sensor)
{
struct device_node *node = dev->of_node;
struct device_node *ep_node;
int ret;
/* 解析时钟 */
sensor->xvclk = devm_clk_get(dev, "xvclk");
if (IS_ERR(sensor->xvclk)) {
dev_err(dev, "failed to get xvclk\n");
return PTR_ERR(sensor->xvclk);
}
/* 解析电源 regulator */
sensor->avdd = devm_regulator_get(dev, "avdd");
sensor->dvdd = devm_regulator_get(dev, "dvdd");
sensor->dovdd = devm_regulator_get(dev, "dovdd");
if (IS_ERR(sensor->avdd) || IS_ERR(sensor->dvdd) || IS_ERR(sensor->dovdd)) {
dev_err(dev, "failed to get regulators\n");
return -ENODEV;
}
/* 解析 GPIO 复位引脚 */
sensor->reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH);
if (IS_ERR(sensor->reset_gpio)) {
dev_err(dev, "failed to get reset gpio\n");
return PTR_ERR(sensor->reset_gpio);
}
/* 解析 endpoint 属性(数据通道、链路频率) */
ep_node = of_graph_get_next_endpoint(node, NULL);
if (!ep_node) {
dev_err(dev, "no endpoint found\n");
return -EINVAL;
}
ret = of_property_read_u32_array(ep_node, "data-lanes",
sensor->data_lanes, 2);
if (ret < 0) {
dev_err(dev, "failed to read data-lanes\n");
of_node_put(ep_node);
return ret;
}
of_node_put(ep_node);
return 0;
}
常见陷阱: 设备树中的
reg地址必须与I2C从设备地址一致;clock-names和supply-names必须与驱动中devm_clk_get和devm_regulator_get的第二个参数完全匹配,否则解析会失败。
4.3 电源管理:regulator与clock
Sensor对电源时序和时钟频率有严格要求。错误的电源管理可能导致图像异常甚至硬件损坏。
4.3.1 Regulator控制
Regulator用于控制Sensor的模拟电源(AVDD)、数字电源(DVDD)和I/O电源(DOVDD)。典型电压值如下表:
| 电源名称 | 典型电压 | 用途 | 上电顺序 |
|---|---|---|---|
| AVDD | 2.8V | 模拟电路供电 | 第1步 |
| DVDD | 1.2V | 数字核心供电 | 第2步 |
| DOVDD | 1.8V | I/O接口供电 | 第3步 |
4.3.2 Clock控制
Sensor需要外部主时钟(xvclk)驱动内部PLL。驱动中应设置正确的时钟频率并确保时钟稳定后再进行寄存器配置。
static int sensor_power_on(struct sensor_state *sensor)
{
int ret;
/* 1. 使能 AVDD */
ret = regulator_enable(sensor->avdd);
if (ret < 0)
return ret;
usleep_range(1000, 2000); /* 等待电压稳定 */
/* 2. 使能 DVDD */
ret = regulator_enable(sensor->dvdd);
if (ret < 0)
goto err_dvdd;
usleep_range(1000, 2000);
/* 3. 使能 DOVDD */
ret = regulator_enable(sensor->dovdd);
if (ret < 0)
goto err_dovdd;
usleep_range(1000, 2000);
/* 4. 设置并使能时钟 */
ret = clk_set_rate(sensor->xvclk, 24000000); /* 24MHz */
if (ret < 0)
goto err_clk;
ret = clk_prepare_enable(sensor->xvclk);
if (ret < 0)
goto err_clk;
usleep_range(5000, 10000); /* 等待时钟稳定 */
/* 5. 释放复位(低电平有效) */
gpiod_set_value_cansleep(sensor->reset_gpio, 0);
usleep_range(10000, 20000);
return 0;
err_clk:
regulator_disable(sensor->dovdd);
err_dovdd:
regulator_disable(sensor->dvdd);
err_dvdd:
regulator_disable(sensor->avdd);
return ret;
}
4.4 v4l2_subdev注册
完成硬件初始化后,驱动需要将Sensor注册为V4L2子设备,以便上层框架能够调用其操作函数。
4.4.1 v4l2_subdev结构体初始化
static const struct v4l2_subdev_ops sensor_subdev_ops = {
.core = &sensor_core_ops,
.video = &sensor_video_ops,
.pad = &sensor_pad_ops,
};
static const struct v4l2_subdev_internal_ops sensor_internal_ops = {
.registered = sensor_registered,
.unregistered = sensor_unregistered,
};
static int sensor_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
struct sensor_state *sensor;
struct v4l2_subdev *sd;
int ret;
sensor = devm_kzalloc(&client->dev, sizeof(*sensor), GFP_KERNEL);
if (!sensor)
return -ENOMEM;
/* 设备树解析 */
ret = sensor_parse_dt(&client->dev, sensor);
if (ret < 0)
return ret;
/* 电源上电 */
ret = sensor_power_on(sensor);
if (ret < 0)
return ret;
/* 硬件初始化(写入寄存器序列) */
ret = sensor_init_regs(sensor);
if (ret < 0)
goto err_power_off;
/* 初始化 v4l2_subdev */
sd = &sensor->sd;
v4l2_i2c_subdev_init(sd, client, &sensor_subdev_ops);
sd->internal_ops = &sensor_internal_ops;
sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
sd->entity.function = MEDIA_ENT_F_CAM_SENSOR;
/* 初始化 pad */
sensor->pad.flags = MEDIA_PAD_FL_SOURCE;
ret = media_entity_pads_init(&sd->entity, 1, &sensor->pad);
if (ret < 0)
goto err_power_off;
/* 注册 subdev */
ret = v4l2_async_register_subdev(sd);
if (ret < 0) {
dev_err(&client->dev, "failed to register subdev\n");
goto err_media_cleanup;
}
dev_info(&client->dev, "sensor probe success\n");
return 0;
err_media_cleanup:
media_entity_cleanup(&sd->entity);
err_power_off:
sensor_power_off(sensor);
return ret;
}
4.4.2 关键API说明
| API函数 | 作用 | 注意事项 |
|---|---|---|
v4l2_i2c_subdev_init() |
初始化v4l2_subdev并关联I2C客户端 | 必须在注册前调用 |
media_entity_pads_init() |
初始化媒体实体的pad | pad数量必须与实际硬件一致 |
v4l2_async_register_subdev() |
异步注册subdev到V4L2框架 | 支持设备热插拔和异步绑定 |
v4l2_device_register_subdev() |
同步注册subdev(传统方式) | 需要先有v4l2_device实例 |
最佳实践: 推荐使用
v4l2_async_register_subdev()进行异步注册,这样Sensor驱动可以与CSI接收器驱动解耦,提高系统灵活性。在registered回调中完成媒体链接的创建。
4.5 完整probe函数模板
以下是一个整合了上述所有步骤的probe函数模板,适用于大多数I2C接口的Sensor驱动:
static int sensor_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
struct sensor_state *sensor;
struct v4l2_subdev *sd;
int ret;
/* 1. 分配私有数据结构 */
sensor = devm_kzalloc(&client->dev, sizeof(*sensor), GFP_KERNEL);
if (!sensor)
return -ENOMEM;
/* 2. 解析设备树 */
ret = sensor_parse_dt(&client->dev, sensor);
if (ret < 0)
return ret;
/* 3. 电源上电 */
ret = sensor_power_on(sensor);
if (ret < 0)
return ret;
/* 4. 读取芯片ID,验证硬件连接 */
ret = sensor_check_id(sensor);
if (ret < 0) {
dev_err(&client->dev, "chip ID mismatch\n");
goto err_power_off;
}
/* 5. 初始化寄存器序列 */
ret = sensor_init_regs(sensor);
if (ret < 0)
goto err_power_off;
/* 6. 初始化V4L2 subdev */
sd = &sensor->sd;
v4l2_i2c_subdev_init(sd, client, &sensor_subdev_ops);
sd->internal_ops = &sensor_internal_ops;
sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
sd->entity.function = MEDIA_ENT_F_CAM_SENSOR;
/* 7. 初始化pad */
sensor->pad.flags = MEDIA_PAD_FL_SOURCE;
ret = media_entity_pads_init(&sd->entity, 1, &sensor->pad);
if (ret < 0)
goto err_power_off;
/* 8. 异步注册subdev */
ret = v4l2_async_register_subdev(sd);
if (ret < 0) {
dev_err(&client->dev, "failed to register subdev\n");
goto err_media_cleanup;
}
dev_info(&client->dev, "sensor probe success\n");
return 0;
err_media_cleanup:
media_entity_cleanup(&sd->entity);
err_power_off:
sensor_power_off(sensor);
return ret;
}
关键检查点: 在probe函数中,务必在注册subdev之前完成所有硬件初始化操作。一旦subdev注册成功,其他内核组件可能会立即调用subdev的操作函数,此时必须确保硬件已处于可用状态。
4.6 本章小结
Sensor驱动的probe函数是驱动开发的基石。本章详细介绍了设备树解析、电源管理(regulator与clock)以及v4l2_subdev注册的完整流程。掌握这些内容后,开发者可以构建出健壮、可移植的Sensor驱动,为后续的ISP协同工作打下坚实基础。