3. Sensor 驱动与初始化

Sensor 驱动是 ISP Pipeline 的起点。驱动负责完成 Sensor 上电、时钟配置、寄存器初始化、分辨率/帧率切换等关键操作。本章深入分析 Sensor 驱动模型、I2C/SPI 通信、寄存器读写机制、分辨率/帧率设置以及 PLL 配置,帮助学员掌握从驱动加载到图像数据流产生的完整链路。

3.1 Sensor 驱动模型

在 Linux 内核中,Sensor 驱动通常基于 V4L2(Video for Linux 2)子设备框架实现。驱动模型分为三层:

层级 组件 职责
应用层 用户空间程序(如 libcamera、gstreamer) 通过 V4L2 接口打开设备、设置格式、启动/停止流
核心层 V4L2 子设备框架 + Media Controller 管理设备注册、pipeline 路由、事件通知
硬件层 Sensor 驱动(I2C/SPI 客户端) 直接操作 Sensor 寄存器,实现初始化、曝光、增益、帧率控制
关键点:Sensor 驱动必须实现 v4l2_subdev_ops 中的 core.s_power、video.s_stream、pad.set_fmt 等回调函数,才能被上层 Pipeline 正确调用。
Sensor 驱动模型分层架构 用户空间应用(libcamera / gstreamer) V4L2 子设备框架 + Media Controller Sensor 驱动(I2C/SPI 客户端) Sensor 硬件(寄存器 / MIPI / 时钟) 驱动注册流程 i2c_new_client_device v4l2_i2c_subdev_init media_entity_pads_init

3.2 I2C/SPI 配置

Sensor 与主控 SoC 之间通常通过 I2C 或 SPI 总线进行寄存器读写。两种总线的选择取决于 Sensor 的寄存器空间大小、速度要求和引脚数量。

特性 I2C SPI
引脚数 2(SDA + SCL) 4(MOSI + MISO + SCLK + CS)
速度 标准 100kHz / 快速 400kHz / 高速 3.4MHz 通常 10MHz ~ 50MHz
寻址方式 7位或10位设备地址 + 寄存器地址 片选 + 寄存器地址(通常 16位)
适用场景 寄存器较少、对引脚敏感的中低端 Sensor 寄存器较多、需要快速配置的高端 Sensor
实战建议:在设备树中配置 I2C/SPI 总线频率时,务必参考 Sensor 数据手册的最大支持频率。过高的频率可能导致通信不稳定,尤其在长走线或高噪声环境中。

3.2.1 设备树配置示例(I2C)

/* imx219 Sensor 设备树节点 */
&i2c2 {
    status = "okay";
    clock-frequency = <400000>;  /* 400kHz 快速模式 */

    imx219: camera@10 {
        compatible = "sony,imx219";
        reg = <0x10>;               /* I2C 7位地址 */
        clocks = <&clks IMX219_CLK>;
        clock-names = "xvclk";
        clock-frequency = <24000000>; /* 24MHz 主时钟 */
        reset-gpios = <&gpio1 15 GPIO_ACTIVE_LOW>;
        powerdown-gpios = <&gpio1 16 GPIO_ACTIVE_HIGH>;

        port {
            imx219_ep: endpoint {
                remote-endpoint = <&csi_ep>;
                data-lanes = <1 2>;
                link-frequencies = /bits/ 64 <456000000>;
            };
        };
    };
};

3.2.2 SPI 配置示例

/* ov5640 Sensor 使用 SPI 接口 */
&spi1 {
    status = "okay";
    cs-gpios = <&gpio3 20 GPIO_ACTIVE_LOW>;

    ov5640: camera@0 {
        compatible = "ovti,ov5640";
        reg = <0>;                 /* SPI 片选索引 */
        spi-max-frequency = <20000000>; /* 20MHz */
        clocks = <&clks OV5640_CLK>;
        clock-names = "xclk";
        clock-frequency = <24000000>;
        reset-gpios = <&gpio4 5 GPIO_ACTIVE_LOW>;
        powerdown-gpios = <&gpio4 6 GPIO_ACTIVE_HIGH>;

        port {
            ov5640_ep: endpoint {
                remote-endpoint = <&csi_ep>;
                data-lanes = <1 2 3 4>;
                link-frequencies = /bits/ 64 <480000000>;
            };
        };
    };
};

3.3 寄存器读写

Sensor 驱动的核心操作是寄存器读写。Linux 内核提供了 i2c_smbus_read_word_data、i2c_smbus_write_word_data 等辅助函数,但更常见的做法是使用 regmap 框架统一管理寄存器访问。

推荐做法:使用 regmap 封装 I2C/SPI 读写,可以自动处理缓存、字节序、多字节传输等问题,同时方便调试(通过 debugfs 导出寄存器值)。
/* 使用 regmap 实现寄存器读写 */
static const struct regmap_config sensor_regmap_config = {
    .reg_bits = 16,          /* 寄存器地址 16位 */
    .val_bits = 8,           /* 寄存器值 8位 */
    .max_register = 0xffff,
    .cache_type = REGCACHE_RBTREE,
};

static int sensor_probe(struct i2c_client *client)
{
    struct regmap *regmap;

    regmap = devm_regmap_init_i2c(client, &sensor_regmap_config);
    if (IS_ERR(regmap))
        return PTR_ERR(regmap);

    /* 通过 regmap 读写寄存器 */
    regmap_write(regmap, 0x0103, 0x01);  /* 软件复位 */
    msleep(10);
    regmap_read(regmap, 0x0000, &chip_id);
    dev_info(&client->dev, "Sensor chip ID: 0x%04x\n", chip_id);

    return 0;
}

3.3.1 寄存器读写时序

I2C 寄存器写时序(16位地址 + 8位数据) 主控 设备地址 Reg Addr[15:8] Reg Addr[7:0] 数据 写时序说明 主控发送设备地址(写)→ 寄存器高字节 → 寄存器低字节 → 数据 → 停止条件

3.4 分辨率/帧率设置

分辨率与帧率的设置本质上是修改 Sensor 内部的行/帧长度寄存器、时钟分频系数以及 PLL 倍频参数。驱动通过预定义的配置表(sensor_mode)来切换不同的工作模式。

3.4.1 配置表结构

/* Sensor 模式配置表 */
struct sensor_mode {
    u32 width;
    u32 height;
    u32 fps;           /* 目标帧率,单位:fps */
    u32 hts;           /* 水平总长度(包括 blanking) */
    u32 vts;           /* 垂直总长度(包括 blanking) */
    u32 pll_mult;      /* PLL 倍频系数 */
    u32 pre_div;       /* PLL 预分频 */
    const struct regval_list *regs; /* 寄存器配置序列 */
    int num_regs;
};

static const struct sensor_mode sensor_modes[] = {
    {
        .width = 1920,
        .height = 1080,
        .fps = 30,
        .hts = 2200,
        .vts = 1125,
        .pll_mult = 48,
        .pre_div = 2,
        .regs = mode_1920x1080_30fps_regs,
        .num_regs = ARRAY_SIZE(mode_1920x1080_30fps_regs),
    },
    {
        .width = 1280,
        .height = 720,
        .fps = 60,
        .hts = 1650,
        .vts = 750,
        .pll_mult = 36,
        .pre_div = 1,
        .regs = mode_1280x720_60fps_regs,
        .num_regs = ARRAY_SIZE(mode_1280x720_60fps_regs),
    },
};

3.4.2 帧率计算公式

帧率由 Sensor 内部时钟频率、水平总长度(HTS)和垂直总长度(VTS)共同决定:

/* 帧率计算公式 */
pixel_clock = input_clock * pll_mult / pre_div;
frame_rate = pixel_clock / (hts * vts);

/* 示例:24MHz 输入时钟,PLL 倍频 48,预分频 2 */
pixel_clock = 24MHz * 48 / 2 = 576MHz;
frame_rate = 576MHz / (2200 * 1125) ≈ 232.7fps;  /* 实际需考虑 MIPI 数据速率限制 */
注意:实际帧率还受到 MIPI CSI 带宽、ISP 处理能力、DDR 带宽等多方面限制。驱动中设置的帧率是 Sensor 内部的理论最大值,最终输出帧率可能因下游瓶颈而降低。

3.5 PLL 配置

Sensor 内部的 PLL(锁相环)用于将外部输入时钟倍频到 Sensor 内部所需的工作频率。PLL 配置直接影响像素时钟、MIPI 数据速率以及帧率。

3.5.1 PLL 典型结构

Sensor PLL 时钟树结构 输入时钟 24MHz 预分频 ÷2 VCO ×48 像素时钟 MIPI 时钟 PLL 配置关键参数 VCO 频率 = 输入时钟 / 预分频 × 倍频系数 像素时钟 = VCO 频率 / 像素分频 MIPI 时钟 = VCO 频率 / MIPI 分频 典型 VCO 频率范围:400MHz ~ 800MHz(视 Sensor 型号而定)

3.5.2 PLL 配置代码示例

/* Sensor PLL 配置函数 */
static int sensor_set_pll(struct regmap *regmap,
                          u32 input_clk, u32 target_pixel_clk)
{
    u32 pre_div, mult, vco_freq;
    int ret;

    /* 1. 选择预分频,使 VCO 频率落在合理范围 */
    for (pre_div = 1; pre_div <= 8; pre_div++) {
        vco_freq = target_pixel_clk * pre_div;
        if (vco_freq >= 400000000 && vco_freq <= 800000000)
            break;
    }

    /* 2. 计算倍频系数 */
    mult = vco_freq / (input_clk / pre_div);

    /* 3. 写入 PLL 寄存器 */
    ret = regmap_write(regmap, 0x0300, pre_div);  /* 预分频寄存器 */
    if (ret)
        return ret;

    ret = regmap_write(regmap, 0x0302, mult);     /* 倍频系数寄存器 */
    if (ret)
        return ret;

    /* 4. 等待 PLL 锁定 */
    msleep(20);

    /* 5. 检查 PLL 锁定状态 */
    ret = regmap_read(regmap, 0x0303, &status);
    if (ret)
        return ret;

    if (!(status & 0x01)) {
        dev_err(regmap_get_device(regmap), "PLL lock failed\n");
        return -EIO;
    }

    return 0;
}

3.5.3 常见 PLL 配置参数对比

Sensor 型号 输入时钟 预分频 倍频系数 VCO 频率 像素时钟 MIPI 速率
IMX219 24MHz 2 48 576MHz 144MHz 456Mbps/lane
OV5640 24MHz 1 40 960MHz 120MHz 480Mbps/lane
IMX290 37.125MHz 3 54 668.25MHz 148.5MHz 594Mbps/lane
调优技巧:PLL 配置时,优先保证 VCO 频率在 Sensor 数据手册推荐的范围内(通常 400MHz~800MHz)。VCO 频率过高会导致功耗增加和抖动恶化,过低则无法锁定或输出时钟质量差。

3.6 初始化流程总结

Sensor 驱动的完整初始化流程如下:

  1. 设备树解析:获取时钟频率、GPIO、I2C/SPI 总线参数。
  2. 硬件复位:通过 GPIO 拉低复位引脚,保持至少 1ms 后释放。
  3. 时钟使能:启用外部时钟(xvclk),等待时钟稳定(通常 5ms)。
  4. I2C/SPI 通信验证:读取 Sensor 的 Chip ID 寄存器,确认通信正常。
  5. PLL 配置:根据目标分辨率和帧率计算并写入 PLL 参数。
  6. 寄存器初始化序列:写入预定义的寄存器配置表(包括模拟电路、数字电路、输出格式等)。
  7. 模式设置:设置分辨率、帧率、输出格式(RAW8/RAW10/RAW12)。
  8. 启动流:通过 s_stream(1) 回调使能 Sensor 输出 MIPI 数据。
Sensor 驱动初始化流程图 1. 设备树解析 2. 硬件复位 3. 时钟使能 4. 读取 Chip ID ID 匹配? 返回 -ENODEV 继续初始化