3. Sensor驱动基础

做Camera驱动,绕不开V4L2。我刚开始接触RK平台时,也被这套框架搞得有点晕。说白了,V4L2就是Linux内核给视频设备定的一套标准接口。你写的Sensor驱动,最终都要挂到这个框架下面。

这一节,我们就把V4L2框架、驱动注册流程、设备树配置这三块串起来讲。嗯,都是基本功,但坑也不少。

3.1 V4L2框架:Camera驱动的“高速公路”

V4L2的全称是Video for Linux 2。它把视频设备抽象成文件节点——/dev/videoX。用户空间程序通过open、ioctl、mmap这些标准系统调用,就能操作摄像头。

你想想看,如果没有V4L2,每个Sensor芯片都要搞一套私有API,那上层应用得疯掉。

V4L2框架的核心分层,我画了张图:

V4L2 框架核心分层 用户空间应用 (Camera App / GStreamer / V4L2-utils) 系统调用层 (open / ioctl / mmap / poll) V4L2 核心 (videodev.ko / v4l2-dev.c) Bridge 驱动 (RKISP / RKCIF / 平台CSI接口) Sensor 驱动 (IMX415 / OV5648 / GC系列等) 硬件层 (Sensor芯片 / MIPI CSI / I2C总线) 用户空间 内核空间

从下往上看:

  • 硬件层:Sensor芯片通过MIPI CSI接口接到SoC,I2C总线用来读写寄存器。
  • Sensor驱动:你写的核心代码。负责初始化Sensor、配置分辨率/帧率、控制曝光/增益。
  • Bridge驱动:RK平台的ISP或CIF接口驱动。它把Sensor传来的RAW数据送到ISP管线。
  • V4L2核心:内核提供的通用框架。帮你注册字符设备、处理ioctl命令。
  • 系统调用层:用户空间通过标准接口与内核通信。
我的习惯:调试Sensor驱动时,先用v4l2-ctl --list-devices看看设备节点有没有生成。如果/dev/videoX没出来,八成是驱动注册或设备树匹配出了问题。

3.2 Sensor驱动注册流程:从probe到stream on

一个典型的Sensor驱动,注册流程大致分五步。我在项目中调过十几款Sensor,流程基本一致,但细节上各家芯片各有脾气。

步骤 函数/操作 说明
1. 匹配设备 probe() 内核根据设备树匹配到驱动,调用probe函数
2. 初始化硬件 sensor_init() 读取芯片ID、配置PLL、设置默认分辨率
3. 注册V4L2子设备 v4l2_i2c_subdev_init() 将Sensor注册为V4L2子设备,挂到Bridge驱动下
4. 实现操作函数集 sensor_subdev_ops 填充s_power、s_stream、enum_mbus_code等回调
5. 注册控制项 v4l2_ctrl_handler_setup() 注册曝光、增益、白平衡等V4L2控制

下面是一个简化版的probe函数示例。嗯,我删掉了错误处理和一些平台宏,保留主干逻辑:

static int sensor_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
    struct sensor_dev *sensor;
    int ret;

    // 1. 分配驱动私有数据结构体
    sensor = devm_kzalloc(&client->dev, sizeof(*sensor), GFP_KERNEL);
    if (!sensor)
        return -ENOMEM;

    // 2. 读取Sensor芯片ID,确认硬件存在
    ret = sensor_read_reg(client, SENSOR_REG_CHIP_ID, &sensor->chip_id);
    if (ret < 0 || sensor->chip_id != SENSOR_EXPECTED_ID) {
        dev_err(&client->dev, "chip ID mismatch: 0x%04x\n", sensor->chip_id);
        return -ENODEV;
    }

    // 3. 初始化V4L2子设备
    v4l2_i2c_subdev_init(&sensor->sd, client, &sensor_subdev_ops);
    sensor->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;

    // 4. 初始化控制句柄
    v4l2_ctrl_handler_init(&sensor->ctrl_handler, 4);
    sensor->ctrl_hdl = v4l2_ctrl_new_std(&sensor->ctrl_handler,
                                          &sensor_ctrl_ops,
                                          V4L2_CID_EXPOSURE,
                                          0, 0xFFFF, 1, 0x1000);
    sensor->sd.ctrl_handler = &sensor->ctrl_handler;

    // 5. 注册子设备
    ret = v4l2_async_register_subdev(&sensor->sd);
    if (ret < 0) {
        dev_err(&client->dev, "failed to register subdev\n");
        return ret;
    }

    dev_info(&client->dev, "sensor probe success, chip ID: 0x%04x\n",
             sensor->chip_id);
    return 0;
}
我曾经踩过的坑:芯片ID读取失败是最常见的问题。有一次我调GC2053,I2C通信正常,但读回来的ID总是0xFF。查了两天才发现——Sensor的供电时序不对,DVDD比AVDD早上电了2ms。嗯,硬件时序问题,驱动层面很难定位。

3.3 设备树配置:驱动与硬件的“连接器”

设备树(Device Tree)是RK平台描述硬件资源的方式。Sensor驱动通过设备树知道:接在哪个I2C总线上、使用哪个CSI接口、电源怎么控制。

一个典型的Sensor设备树节点长这样:

&i2c4 {
    status = "okay";
    clock-frequency = <400000>;

    imx415: camera@1a {
        compatible = "sony,imx415";
        reg = <0x1a>;
        clocks = <&cru CLK_MIPI_CAMARAOUT_M4>;
        clock-names = "xvclk";
        pinctrl-names = "default";
        pinctrl-0 = <&cif_clk>;

        power-domains = <&power RK3588_PD_VI>;
        avdd-supply = <&vcc_avdd_2v8>;
        dovdd-supply = <&vcc_dovdd_1v8>;
        dvdd-supply = <&vcc_dvdd_1v2>;

        reset-gpios = <&gpio1 RK_PB2 GPIO_ACTIVE_LOW>;
        pwdn-gpios = <&gpio1 RK_PB3 GPIO_ACTIVE_HIGH>;

        rockchip,camera-module-index = <0>;
        rockchip,camera-module-facing = "back";

        port {
            imx415_out: endpoint {
                remote-endpoint = <&csi2_in>;
                data-lanes = <1 2 3 4>;
            };
        };
    };
};

关键字段解释:

  • compatible:驱动中of_match_table匹配的依据。名字要严格对应。
  • reg:I2C设备地址。IMX415是0x1a,OV5648一般是0x36。
  • clocks:Sensor的主时钟(xvclk)。RK平台通常由Cru模块提供。
  • supply:三路供电——模拟2.8V、数字IO 1.8V、核心1.2V。
  • reset-gpios / pwdn-gpios:复位和掉电引脚。注意电平极性。
  • data-lanes:MIPI数据通道数。4-lane还是2-lane,这里配错的话图像会花掉。
核心要点:设备树配置错误是驱动调不通的首要原因。我建议你在调试初期,先用cat /sys/kernel/debug/gpio确认GPIO状态,用i2cdetect -y 4确认I2C地址是否正确。

3.4 驱动与设备树的匹配机制

内核怎么把设备树节点和驱动代码对上?靠of_match_table。驱动中需要定义一个匹配表:

static const struct of_device_id sensor_of_match[] = {
    { .compatible = "sony,imx415" },
    { .compatible = "ovti,ov5648" },
    { /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, sensor_of_match);

static struct i2c_driver sensor_i2c_driver = {
    .driver = {
        .name = "rk_sensor",
        .of_match_table = sensor_of_match,
    },
    .probe = sensor_probe,
    .id_table = sensor_id_table,
};

流程很简单:内核解析设备树 → 找到compatible为"sony,imx415"的节点 → 遍历已注册的I2C驱动 → 匹配到sensor_of_match → 调用probe函数。

这里有个细节:MODULE_DEVICE_TABLE宏会把匹配表编译进模块的.modinfo段。如果你把驱动编译成模块,modprobe时内核会读取这个信息来自动加载。

我建议:调试阶段把驱动编进内核(y),而不是模块(m)。这样启动时驱动自动加载,省去手动insmod的麻烦。等驱动稳定了再改成模块。

3.5 避坑指南:设备树常见错误

这些年我见过的设备树问题,列几个典型的:

  1. I2C地址错误:Sensor的I2C地址是7位还是8位?设备树里写7位地址(如0x1a),但i2cdetect显示的是8位值(0x34)。别搞混了。
  2. 供电顺序:有些Sensor要求AVDD先上电,DVDD后上。设备树里supply的顺序不代表上电顺序,需要用regulator-boot-on或驱动里手动控制。
  3. MIPI时钟频率:设备树里配的clock-frequency要和Sensor的PLL配置匹配。频率不对,图像会闪或者完全没图。
  4. GPIO复用冲突:RK平台的GPIO经常被多个外设共用。检查pinctrl配置,确保Sensor的reset/pwdn引脚没有被其他模块占用。

嗯,设备树这块,说白了就是“对号入座”。每个字段都要和硬件原理图、Sensor datasheet一一对应。我每次拿到新板子,第一件事就是把原理图和设备树对照着看一遍,能省掉后面很多调试时间。