4. Sensor驱动框架:高通平台下Sensor驱动文件结构、probe/remove回调函数

在高通Camera HAL层架构中,Sensor驱动是连接硬件传感器与上层ISP(图像信号处理器)Pipeline的桥梁。本章将深入剖析高通平台下Sensor驱动的标准文件结构、核心数据结构以及probe/remove回调函数的实现机制,帮助学员建立完整的驱动框架认知。

4.1 驱动文件结构总览

高通平台下的Sensor驱动通常遵循V4L2(Video for Linux 2)子设备驱动模型,并融合了高通特有的Camera Subsystem(CAMSS)框架。一个标准的Sensor驱动工程包含以下核心文件:

文件/目录 功能描述 关键内容
sensor_name.c 主驱动源文件,实现V4L2子设备操作集 probe、remove、s_power、s_stream
sensor_name.h 头文件,定义寄存器地址、结构体、枚举 Sensor ID、模式表、曝光/增益寄存器映射
sensor_name_reg.c 寄存器配置表,包含各分辨率/帧率的初始化序列 二维数组形式的寄存器-值对
Kconfig 内核配置选项,使能/禁用该Sensor驱动 config VIDEO_SENSOR_NAME
Makefile 编译规则,将驱动编译为模块或内置 obj-$(CONFIG_VIDEO_SENSOR_NAME) += sensor_name.o
dts/ 设备树文件,描述硬件连接与电源时序 I2C地址、GPIO引脚、时钟频率、电源域
实战提示: 在高通SM8250/8450等平台中,Sensor驱动通常位于 kernel/msm-5.x/drivers/media/platform/msm/camera_v2/sensor/ 目录下。调试时优先检查 dts 中的电源配置是否正确,这是 probe 失败的最常见原因。

4.2 核心数据结构:从设备树到驱动实例

高通Sensor驱动的核心数据结构围绕 v4l2_subdev 和 msm_sensor_ctrl_t 展开。下图展示了驱动实例化过程中各数据结构的关系:

高通Sensor驱动核心数据结构关系图 设备树节点 (dts: i2c@...) platform_driver (of_match_table) i2c_driver (id_table) msm_sensor_ctrl_t (私有数据,包含i2c_client) power_info / sensordata v4l2_subdev (V4L2子设备实例) subdev_ops / video_ops 包含 subdev_ops core / s_power / s_stream video_ops g_mbus_fmt / s_mbus_fmt ctrl_ops s_ctrl / g_ctrl 说明: probe() 函数负责分配 msm_sensor_ctrl_t,初始化 i2c_client,注册 v4l2_subdev; remove() 函数执行逆序操作:注销 subdev、释放电源、释放内存。

4.3 probe回调函数:驱动的“构造函数”

probe 函数是Sensor驱动的入口点,当内核发现设备树中匹配的节点或I2C从设备时被调用。其核心职责包括:

  1. 解析设备树:获取电源、GPIO、时钟等资源信息。
  2. 分配私有数据结构:为 msm_sensor_ctrl_t 分配内存。
  3. 初始化I2C通信:设置 i2c_client 的地址和传输速率。
  4. 读取Sensor ID:通过I2C读取芯片ID寄存器,验证硬件是否存在。
  5. 注册V4L2子设备:调用 v4l2_i2c_subdev_init() 或 v4l2_spi_subdev_init()。
  6. 初始化电源管理:注册 regulator 和 gpio 控制接口。

以下是一个简化但完整的 probe 函数代码示例:

static int sensor_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
    int rc = 0;
    struct msm_sensor_ctrl_t *s_ctrl = NULL;
    struct device *dev = &client->dev;

    // 1. 分配私有数据结构
    s_ctrl = devm_kzalloc(dev, sizeof(struct msm_sensor_ctrl_t), GFP_KERNEL);
    if (!s_ctrl) {
        dev_err(dev, "Failed to allocate sensor control\n");
        return -ENOMEM;
    }

    // 2. 保存i2c_client指针
    s_ctrl->sensor_i2c_client = client;
    i2c_set_clientdata(client, s_ctrl);

    // 3. 解析设备树电源配置
    rc = msm_sensor_power_init(s_ctrl, dev);
    if (rc < 0) {
        dev_err(dev, "Power init failed: %d\n", rc);
        goto power_fail;
    }

    // 4. 读取Sensor ID验证硬件
    rc = sensor_read_id(s_ctrl);
    if (rc < 0) {
        dev_err(dev, "Sensor ID mismatch\n");
        goto id_fail;
    }

    // 5. 初始化V4L2子设备
    v4l2_i2c_subdev_init(&s_ctrl->sensor_v4l2_subdev, client,
                         &sensor_subdev_ops);
    s_ctrl->sensor_v4l2_subdev.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;

    // 6. 注册媒体实体
    rc = media_entity_pads_init(&s_ctrl->sensor_v4l2_subdev.entity,
                                1, &s_ctrl->pad);
    if (rc < 0) {
        dev_err(dev, "Media entity init failed\n");
        goto media_fail;
    }

    dev_info(dev, "Sensor probe successful\n");
    return 0;

media_fail:
id_fail:
    msm_sensor_power_deinit(s_ctrl);
power_fail:
    devm_kfree(dev, s_ctrl);
    return rc;
}
调试陷阱: 在probe函数中,sensor_read_id() 是最容易失败的地方。常见原因包括:I2C地址错误(设备树与硬件不匹配)、电源时序未满足(需要先上电再读ID)、时钟频率超出Sensor规格。建议在读取ID前后增加 dev_dbg 打印寄存器值。

4.4 remove回调函数:驱动的“析构函数”

remove 函数在驱动卸载或设备热插拔时被调用,必须确保所有资源被正确释放,避免内存泄漏或内核崩溃。其标准执行流程如下:

步骤 操作 对应函数/宏
1 停止视频流(如果正在运行) sensor_stream_off()
2 关闭电源(regulator_disable、gpio_set_value) msm_sensor_power_deinit()
3 注销V4L2子设备 v4l2_device_unregister_subdev()
4 清理媒体实体 media_entity_cleanup()
5 释放私有数据结构内存 devm_kfree() 或 kfree()

典型实现代码:

static int sensor_remove(struct i2c_client *client)
{
    struct msm_sensor_ctrl_t *s_ctrl = i2c_get_clientdata(client);
    struct v4l2_subdev *subdev = &s_ctrl->sensor_v4l2_subdev;

    if (!s_ctrl) {
        dev_err(&client->dev, "Sensor control is NULL\n");
        return -EINVAL;
    }

    // 1. 确保流已停止
    if (s_ctrl->stream_state == STREAM_ON) {
        sensor_stream_off(s_ctrl);
    }

    // 2. 关闭电源
    msm_sensor_power_deinit(s_ctrl);

    // 3. 注销V4L2子设备
    v4l2_device_unregister_subdev(subdev);
    media_entity_cleanup(&subdev->entity);

    // 4. 释放私有数据(devm_* 系列可自动释放,但显式释放更安全)
    devm_kfree(&client->dev, s_ctrl);
    i2c_set_clientdata(client, NULL);

    dev_info(&client->dev, "Sensor removed successfully\n");
    return 0;
}

4.5 probe/remove的调用时序与依赖关系

理解 probe 和 remove 在系统启动/关闭过程中的调用顺序,对于调试电源管理问题至关重要。下图展示了从系统启动到Sensor可用的完整时序:

probe/remove 调用时序图 时间 设备树解析 of_match_table probe() 调用 分配资源 subdev注册 v4l2_i2c_subdev_init s_stream (应用层) remove() 逆序流程 s_stream(off) subdev注销 电源关闭 释放内存 关键依赖: probe 必须在 s_stream(on) 之前完成;remove 必须在 s_stream(off) 之后执行。 电源域必须在 probe 中使能,在 remove 中关闭,否则会导致 I2C 通信失败。

4.6 驱动注册与匹配机制

高通平台支持两种驱动注册方式:I2C驱动 和 平台驱动。下表对比了两种方式的适用场景与匹配逻辑:

注册方式 匹配依据 适用Sensor类型 probe函数原型
I2C驱动 i2c_device_id 或 of_match_table 标准I2C接口Sensor(OV、IMX系列) probe(struct i2c_client *, const struct i2c_device_id *)
平台驱动 of_match_table 中的compatible字符串 内置CSI接口Sensor、SPI接口Sensor probe(struct platform_device *)

驱动注册的典型代码片段:

// I2C驱动注册示例
static const struct i2c_device_id sensor_id[] = {
    { "sensor_name", 0 },
    { }
};
MODULE_DEVICE_TABLE(i2c, sensor_id);

static const struct of_device_id sensor_of_match[] = {
    { .compatible = "qcom,sensor_name" },
    { }
};
MODULE_DEVICE_TABLE(of, sensor_of_match);

static struct i2c_driver sensor_i2c_driver = {
    .driver = {
        .name = "sensor_name",
        .of_match_table = sensor_of_match,
    },
    .probe = sensor_probe,
    .remove = sensor_remove,
    .id_table = sensor_id,
};

module_i2c_driver(sensor_i2c_driver);
实战要点: 在调试新Sensor时,务必确认设备树中的 compatible 字符串与驱动中的 of_match_table 完全一致(包括大小写)。使用 ls /sys/bus/i2c/devices/ 可以查看I2C设备是否被正确枚举;使用 cat /sys/kernel/debug/devices_deferred 可以查看probe被延迟的原因。

4.7 常见probe失败场景与排查方法

在实际调试中,probe 失败是最常见的问题。以下整理了三种典型失败场景及其排查思路:

  • 场景一:I2C通信失败
    • 现象:sensor_read_id() 返回 -EIO 或 -ENXIO。
    • 排查:用示波器抓取I2C SCL/SDA波形;检查设备树中 reg 地址是否为7位或10位模式;确认Sensor供电电压是否达到数据手册要求。
  • 场景二:电源时序错误
    • 现象:probe成功但 s_stream(on) 时Sensor无输出。
    • 排查:检查 msm_sensor_power_init() 中regulator的使能顺序;确认GPIO复位时序(通常需要先拉低再拉高,保持至少1ms)。
  • 场景三:设备树匹配失败
    • 现象:probe函数根本不被调用。
    • 排查:使用 dtc -I dtb -O dts 反编译设备树,确认节点是否存在;检查 status = "okay" 是否设置;确认 pinctrl 引脚复用配置正确。

通过掌握Sensor驱动的文件结构、probe/remove 回调函数的实现细节以及调试方法,学员可以快速定位并解决高通平台下Sensor驱动的适配问题,为后续的流控制、曝光增益调试打下坚实基础。