3. Linux 内核 I2C/SPI 驱动框架:总线协议对比、设备树配置与 Sensor 寄存器读写基础驱动实现

3.1 I2C 与 SPI 总线协议对比

在 Camera Sensor 驱动开发中,I2C 和 SPI 是最常用的两种板级通信总线。两者在引脚数量、传输速率、通信拓扑和适用场景上存在显著差异。理解这些差异是正确选择总线类型和编写高效驱动的第一步。

对比维度I2C (Inter-Integrated Circuit)SPI (Serial Peripheral Interface)
信号线数量2 线:SCL(时钟)、SDA(数据)4 线:SCLK(时钟)、MOSI(主出从入)、MISO(主入从出)、CS(片选)
通信模式半双工,主从架构全双工,主从架构
最大速率标准模式 100 kHz,快速模式 400 kHz,高速模式 3.4 MHz通常 10~50 MHz,部分可达 100 MHz 以上
从设备寻址7 位或 10 位地址,总线仲裁独立片选线,无地址冲突
总线拓扑多主多从,共享总线单主多从,每个从设备独立 CS
硬件复杂度简单,引脚少,适合空间受限场景引脚多,但无复杂仲裁逻辑
典型 Sensor 应用寄存器配置、低速率控制通道高速图像数据流、批量寄存器读写
关键结论: Camera Sensor 通常使用 I2C 作为控制通道(寄存器读写),使用 SPI 或 MIPI CSI 作为数据通道。部分高端 Sensor 也使用 SPI 进行高速寄存器批量配置,以缩短上电初始化时间。

3.2 设备树(DTS)配置

在 Linux 内核中,设备树(Device Tree)是描述硬件拓扑和资源分配的标准方式。对于 I2C 和 SPI 总线上的 Sensor 设备,DTS 节点需要正确配置总线控制器、设备地址、中断引脚、电源控制 GPIO 以及时钟频率等参数。

3.2.1 I2C Sensor 设备树节点示例

/* imx219 Camera Sensor I2C 节点 */
&i2c2 {
    status = "okay";
    clock-frequency = <400000>;  /* 400 kHz 快速模式 */
    pinctrl-names = "default";
    pinctrl-0 = <&pinctrl_i2c2>;

    imx219: camera-sensor@10 {
        compatible = "sony,imx219";
        reg = <0x10>;               /* 7 位 I2C 地址 */
        clocks = <&clk_ext_camera>;
        clock-names = "xvclk";
        reset-gpios = <&gpio1 15 GPIO_ACTIVE_LOW>;
        powerdown-gpios = <&gpio1 16 GPIO_ACTIVE_HIGH>;
        DOVDD-supply = <&reg_camera_dovdd>;
        AVDD-supply = <&reg_camera_avdd>;
        DVDD-supply = <&reg_camera_dvdd>;

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

3.2.2 SPI Sensor 设备树节点示例

/* SPI Camera Sensor 节点 */
&spi1 {
    status = "okay";
    cs-gpios = <&gpio2 5 GPIO_ACTIVE_LOW>;
    pinctrl-names = "default";
    pinctrl-0 = <&pinctrl_spi1>;

    sensor_spi: camera-sensor@0 {
        compatible = "example,spi-sensor";
        reg = <0>;                 /* SPI 片选索引 0 */
        spi-max-frequency = <20000000>;  /* 20 MHz */
        spi-cpol;                  /* 时钟极性:高电平空闲 */
        spi-cpha;                  /* 时钟相位:第二个边沿采样 */
        reset-gpios = <&gpio3 10 GPIO_ACTIVE_LOW>;
        powerdown-gpios = <&gpio3 11 GPIO_ACTIVE_HIGH>;

        port {
            sensor_spi_ep: endpoint {
                remote-endpoint = <&isp_ep>;
            };
        };
    };
};
DTS 配置要点:
1. I2C 节点必须指定 clock-frequency,确保与 Sensor 数据手册匹配。
2. SPI 节点需明确 spi-cpol 和 spi-cpha 极性,否则通信可能完全失败。
3. 电源控制 GPIO 和 regulator 必须正确关联,避免 Sensor 上电时序异常。

3.3 Sensor 寄存器读写基础驱动实现

Sensor 驱动的核心操作是寄存器的读写。Linux 内核提供了标准的 I2C 和 SPI 客户端 API,驱动开发者只需封装对应的传输函数即可。下面分别给出 I2C 和 SPI 的寄存器读写实现模板。

3.3.1 I2C 寄存器读写实现

#include <linux/i2c.h>
#include <linux/delay.h>

/* 写 16 位寄存器(Sensor 常用 16 位地址 + 8/16 位数据) */
static int sensor_i2c_write_reg(struct i2c_client *client,
                                u16 reg_addr, u8 val)
{
    int ret;
    struct i2c_msg msg;
    u8 buf[3];

    buf[0] = (reg_addr >> 8) & 0xFF;  /* 高字节地址 */
    buf[1] = reg_addr & 0xFF;         /* 低字节地址 */
    buf[2] = val;                     /* 写入数据 */

    msg.addr = client->addr;
    msg.flags = 0;                    /* 写标志 */
    msg.len = 3;
    msg.buf = buf;

    ret = i2c_transfer(client->adapter, &msg, 1);
    if (ret < 0) {
        dev_err(&client->dev, "I2C write reg 0x%04x failed: %d\n",
                reg_addr, ret);
        return ret;
    }
    return 0;
}

/* 读 16 位寄存器 */
static int sensor_i2c_read_reg(struct i2c_client *client,
                               u16 reg_addr, u8 *val)
{
    int ret;
    struct i2c_msg msg[2];
    u8 addr_buf[2];
    u8 data_buf[1];

    addr_buf[0] = (reg_addr >> 8) & 0xFF;
    addr_buf[1] = reg_addr & 0xFF;

    /* 第一段:发送寄存器地址 */
    msg[0].addr = client->addr;
    msg[0].flags = 0;
    msg[0].len = 2;
    msg[0].buf = addr_buf;

    /* 第二段:读取数据 */
    msg[1].addr = client->addr;
    msg[1].flags = I2C_M_RD;
    msg[1].len = 1;
    msg[1].buf = data_buf;

    ret = i2c_transfer(client->adapter, msg, 2);
    if (ret < 0) {
        dev_err(&client->dev, "I2C read reg 0x%04x failed: %d\n",
                reg_addr, ret);
        return ret;
    }
    *val = data_buf[0];
    return 0;
}

3.3.2 SPI 寄存器读写实现

#include <linux/spi/spi.h>

/* SPI 写寄存器:地址 + 数据,一次传输 */
static int sensor_spi_write_reg(struct spi_device *spi,
                                u8 reg_addr, u8 val)
{
    int ret;
    u8 tx_buf[2];

    tx_buf[0] = reg_addr & 0x7F;  /* 写操作:地址最高位为 0 */
    tx_buf[1] = val;

    ret = spi_write(spi, tx_buf, 2);
    if (ret < 0) {
        dev_err(&spi->dev, "SPI write reg 0x%02x failed: %d\n",
                reg_addr, ret);
        return ret;
    }
    return 0;
}

/* SPI 读寄存器:先发地址(读标志),再读数据 */
static int sensor_spi_read_reg(struct spi_device *spi,
                               u8 reg_addr, u8 *val)
{
    int ret;
    u8 tx_buf[2];
    u8 rx_buf[2];
    struct spi_transfer t = {
        .tx_buf = tx_buf,
        .rx_buf = rx_buf,
        .len = 2,
    };
    struct spi_message m;

    tx_buf[0] = reg_addr | 0x80;  /* 读操作:地址最高位为 1 */
    tx_buf[1] = 0x00;             /* dummy byte,用于接收数据 */

    spi_message_init(&m);
    spi_message_add_tail(&t, &m);
    ret = spi_sync(spi, &m);
    if (ret < 0) {
        dev_err(&spi->dev, "SPI read reg 0x%02x failed: %d\n",
                reg_addr, ret);
        return ret;
    }
    *val = rx_buf[1];  /* 第二个字节为读取到的数据 */
    return 0;
}
常见陷阱:
1. I2C 读写组合传输时,必须使用 i2c_transfer 而非两次独立的 i2c_master_send/recv,否则可能因总线释放导致 Sensor 不响应。
2. SPI 的寄存器地址最高位通常用作读写标志位,务必确认 Sensor 数据手册的协议定义。
3. 批量寄存器配置时,优先使用 I2C 的 regmap 框架或 SPI 的 spi_write_then_read,避免频繁调用单字节传输函数。

3.4 I2C 与 SPI 驱动框架对比图

下图展示了 Linux 内核中 I2C 和 SPI 驱动框架的层级结构,以及 Sensor 驱动如何通过内核抽象层与硬件交互。

Linux 内核 I2C / SPI 驱动框架对比 用户空间 (V4L2 / Media Controller) Sensor 驱动 (I2C 客户端) I2C 核心层 (i2c-core) I2C 总线控制器驱动 硬件 I2C 控制器 (SoC) 用户空间 (V4L2 / Media Controller) Sensor 驱动 (SPI 设备) SPI 核心层 (spi-core) SPI 总线控制器驱动 硬件 SPI 控制器 (SoC) vs

3.5 寄存器读写流程与数据流

下图展示了 Sensor 驱动进行寄存器读写时的完整数据流,从用户空间请求到硬件信号产生的全过程。

Sensor 寄存器读写数据流 用户空间应用 (V4L2) Sensor 驱动 (ioctl) 寄存器读写函数 I2C 传输 (i2c_transfer) SPI 传输 (spi_sync) I2C 控制器硬件 SPI 控制器硬件

3.6 驱动开发避坑清单

  • I2C 地址偏移: 部分 Sensor 的 I2C 地址在数据手册中给出的是 8 位地址(左移 1 位),而内核 reg 属性需要 7 位地址,务必确认。
  • SPI 模式匹配: 设备树中的 spi-cpol 和 spi-cpha 必须与 Sensor 要求的 SPI 模式(0/1/2/3)完全一致,否则通信时序错误。
  • 寄存器缓存: 频繁读取的寄存器(如 Sensor 温度、帧计数器)建议在驱动中缓存,减少总线访问次数。
  • 错误恢复: I2C 总线可能因从设备无响应而锁死,驱动应实现超时重试机制,必要时复位总线控制器。
  • 电源时序: 寄存器读写前必须确保 Sensor 的电源域(AVDD、DVDD、IOVDD)已稳定建立,否则可能导致 I2C/SPI 通信失败或设备损坏。
推荐实践: 对于新 Sensor 的驱动开发,建议先在用户空间使用 i2c-tools 或 spidev 验证寄存器读写功能,确认通信正常后再移植到内核驱动中,可大幅降低调试难度。