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 = <®_camera_dovdd>;
AVDD-supply = <®_camera_avdd>;
DVDD-supply = <®_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 节点必须指定
2. SPI 节点需明确
3. 电源控制 GPIO 和 regulator 必须正确关联,避免 Sensor 上电时序异常。
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 读写组合传输时,必须使用
2. SPI 的寄存器地址最高位通常用作读写标志位,务必确认 Sensor 数据手册的协议定义。
3. 批量寄存器配置时,优先使用 I2C 的
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 驱动如何通过内核抽象层与硬件交互。
3.5 寄存器读写流程与数据流
下图展示了 Sensor 驱动进行寄存器读写时的完整数据流,从用户空间请求到硬件信号产生的全过程。
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 验证寄存器读写功能,确认通信正常后再移植到内核驱动中,可大幅降低调试难度。