3. Sensor驱动框架

好,我们直接进入正题。这一章要讲的是Sensor驱动的骨架——说白了,就是驱动怎么“挂”到系统里,怎么和硬件说话,以及内核怎么把你写的驱动叫起来干活。这三个东西搞清楚了,后面写代码就是填空。

3.1 V4L2子系统:Camera驱动的“居委会”

V4L2,全称Video for Linux 2,是Linux内核里专门管视频设备的子系统。你可以把它想象成一个居委会——所有Camera设备都得在它这儿登记,用户空间程序(比如gstreamer、Camera HAL)通过它来统一调度。

我个人习惯把V4L2的层次拆成三层:

  • 底层:Sensor、ISP、MIPI控制器等硬件驱动,负责和寄存器打交道。
  • 中间层:V4L2核心框架,提供统一的设备节点(/dev/videoX)、ioctl接口、buffer管理。
  • 上层:应用层,通过open/ioctl/poll操作设备节点。

在瑞芯微RK平台上,Sensor驱动属于底层。它通过V4L2的subdev(子设备)接口注册到系统中。为什么用subdev?因为一个Camera模组不止Sensor,还有闪光灯、马达、eeprom,每个都是一个子设备。V4L2通过subdev把它们组织起来,统一管理。

核心概念:V4L2 subdev是Sensor驱动的“身份证”。没有它,内核不认识你,应用层也找不到你。

3.2 I2C/SPI通信:和Sensor“打电话”

Sensor和主控芯片之间怎么通信?绝大多数Sensor用I2C,少数高速或特殊Sensor用SPI。瑞芯微平台两种都支持,但I2C是主流。

I2C通信要点:

  • Sensor作为从设备,主控通过I2C总线读写寄存器。
  • 每个Sensor有一个7位或10位地址,驱动里通过i2c_client结构体来操作。
  • 读写函数一般封装成sensor_read_reg/sensor_write_reg,底层调用i2c_transfer。

我在项目中遇到过一个问题:Sensor的I2C地址明明是0x30,但死活读不到数据。后来发现是数据手册上写的是8位地址,而内核用的是7位地址,需要右移一位。嗯,这种坑很常见,大家一定要仔细看手册。

SPI通信要点:

  • SPI是全双工,速度比I2C快,适合传输大量数据。
  • Sensor驱动里一般用spi_write_then_read或spi_sync_transfer。
  • 注意片选信号和时钟极性的配置,错了就通信不上。
特性 I2C SPI
总线占用 2线(SCL+SDA) 4线(SCLK+MOSI+MISO+CS)
速度 标准100k~400k,快的有1M 几十M很常见
设备寻址 软件地址 硬件片选
典型场景 配置寄存器 高速数据流

我的建议:调试I2C/SPI通信时,先拿逻辑分析仪抓波形。别上来就怀疑代码,很多时候是硬件连接或时序问题。

3.3 Sensor驱动入口与probe流程

驱动入口,说白了就是内核怎么找到你的驱动。在Linux设备模型中,驱动通过platform_driver或i2c_driver结构体注册。Sensor驱动通常用i2c_driver,因为Sensor挂在I2C总线上。

一个典型的Sensor驱动入口长这样:

static const struct i2c_device_id sensor_id[] = {
    {"rk_sensor", 0},
    {}
};

MODULE_DEVICE_TABLE(i2c, sensor_id);

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

module_i2c_driver(sensor_i2c_driver);

这里要注意:of_match_table是设备树匹配的关键。设备树里写什么compatible,驱动里就要对应什么。我曾经见过同事把设备树里的compatible写成了"rk,sensor",驱动里写的是"rockchip,sensor",结果驱动死活不probe。这种低级错误,排查起来很浪费时间。

probe流程是驱动初始化的核心。Sensor驱动的probe一般做这几件事:

  1. 获取设备树资源:读取reg、reset-gpios、power-gpios、clock等配置。
  2. 初始化I2C/SPI通信:验证通信是否正常,比如读chip ID。
  3. 分配并初始化V4L2 subdev:设置subdev的ops(操作函数集)。
  4. 注册subdev:调用v4l2_device_register_subdev。
  5. 初始化硬件:上电、复位、配置初始寄存器。

我画了一张流程图,把probe的完整路径展示出来:

Sensor驱动probe流程 i2c_driver.probe 被调用 获取设备树资源 读chip ID验证通信 分配并初始化subdev 注册V4L2 subdev 硬件初始化(上电/复位) probe 成功返回0 chip ID不匹配? 返回 -ENODEV

你看,probe流程其实不复杂。但有一个关键点:chip ID验证。这是probe里最重要的一步。如果读不到正确的chip ID,说明I2C通信有问题,或者设备树配置错了。我建议在probe里加上详细的错误打印,方便调试。

注意:probe函数里不要做耗时操作,比如msleep(1000)。内核的probe是串行执行的,你卡住了,其他设备也得等着。我曾经见过一个驱动在probe里做了500ms的延时,结果整个系统的启动时间被拖慢了。把耗时的初始化放到单独的工作队列里去做。

3.4 设备树匹配:驱动和硬件的“红娘”

设备树(Device Tree)是Linux内核用来描述硬件配置的一种机制。在RK平台上,Sensor的设备树节点一般长这样:

&i2c4 {
    status = "okay";
    rk_sensor: camera-sensor@30 {
        compatible = "rockchip,rk-sensor";
        reg = <0x30>;
        clocks = <&cru CLK_MIPI_CAMERA>;
        clock-names = "xvclk";
        power-gpios = <&gpio2 RK_PB6 GPIO_ACTIVE_HIGH>;
        reset-gpios = <&gpio2 RK_PB7 GPIO_ACTIVE_LOW>;
        pinctrl-names = "default";
        pinctrl-0 = <&cif_clk>;
        rockchip,camera-module-index = <0>;
        rockchip,camera-module-facing = "back";
    };
};

这里有几个关键字段:

  • compatible:驱动和设备树匹配的钥匙,必须和驱动里的of_match_table一致。
  • reg:I2C地址,注意是7位地址。
  • clocks:Sensor的主时钟,一般是24MHz或27MHz。
  • power-gpios/reset-gpios:电源和复位引脚,驱动里通过gpiod_get来获取。

设备树写错了,驱动根本进不了probe。我在项目中遇到过最离谱的一次,是设备树里reg写成了0x60,但Sensor实际地址是0x30。查了两天才发现,原来是数据手册里写的是8位地址0x60,设备树里应该用7位地址0x30。嗯,这种坑踩过一次就记住了。

3.5 总结一下

Sensor驱动框架的核心就三件事:

  • V4L2子系统:提供注册和通信的框架,subdev是Sensor的“身份证”。
  • I2C/SPI通信:和Sensor硬件对话的通道,读写寄存器全靠它。
  • probe流程:驱动初始化的入口,从获取资源到注册subdev,一步都不能少。

这三件事搞明白了,Sensor驱动的基本骨架就有了。后面往里面填代码,就是水到渠成的事。