当前位置:首页>Linux>Linux 驱动 SPI 和I2C 读写函数

Linux 驱动 SPI 和I2C 读写函数

  • 2026-09-28 20:26:40
Linux 驱动 SPI 和I2C 读写函数
大家好,我是王鸽,这篇主要是对 Linux 驱动下的SPI 和I2C 读写函数具体介绍一下,因为之前的文章都是对这两个通讯协议框架流程解读,怎么读写发送数据没有详细解释。

一、核心前提:SPI/I2C 驱动的通用结构

无论是 SPI 还是 I2C,驱动读写的核心逻辑都是:

  1. 从 client/adapter 获取总线句柄;
  2. 组装读写消息(指定地址、长度、数据缓冲区);
  3. 调用总线核心函数执行传输;
  4. 检查返回值并处理错误。
  5. 二、I2C 读写函数总结
1. 核心 API 分类
函数类型
核心函数
适用场景
返回值说明
通用传输
i2c_transfer()
任意格式的 I2C 读写(推荐)
成功:传输的消息数;失败:负数错误码
SMBus 专用
i2c_smbus_read/write_*()
符合 SMBus 规范的简单读写
成功:0 / 读取值;失败:负数
简化接口
i2c_master_send/receive()
纯写 / 纯读(无寄存器地址)
成功:传输字节数;失败:负数

2. 通用读写模板(最常用)

(1)单寄存器读写(带寄存器地址)

#include<linux/i2c.h>// I2C 读单寄存器(核心模板)inti2c_read_reg(struct i2c_client *client, u8 reg, u8 *val){    struct i2c_msg msgs[2] = {        // 消息1:发送寄存器地址(写操作)        {            .addr = client->addr,  // 外设I2C地址(7位)            .flags = 0,            // 0=写,I2C_M_RD=读            .len = 1,              // 寄存器地址长度(1字节)            .buf = &reg,           // 寄存器地址缓冲区        },        // 消息2:读取寄存器值(读操作)        {            .addr = client->addr,            .flags = I2C_M_RD,     // 读标志            .len = 1,              // 读取数据长度            .buf = val,            // 接收缓冲区        },    };    // 执行传输:成功返回消息数(2),失败返回负数    int ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs));    if (ret != ARRAY_SIZE(msgs)) {        dev_err(&client->dev, "I2C读寄存器0x%02x失败,ret=%d\n", reg, ret);        return ret < 0 ? ret : -EIO;    }    return 0;}// I2C 写单寄存器(核心模板)inti2c_write_reg(struct i2c_client *client, u8 reg, u8 val){    u8 buf[2] = {reg, val};  // 寄存器地址 + 写入值    struct i2c_msg msg = {        .addr = client->addr,        .flags = 0,        .len = ARRAY_SIZE(buf),        .buf = buf,    };    int ret = i2c_transfer(client->adapter, &msg, 1);    if (ret != 1) {        dev_err(&client->dev, "I2C写寄存器0x%02x失败,ret=%d\n", reg, ret);        return ret < 0 ? ret : -EIO;    }    return 0;}

还有kernel/drivers/input/touchscreen/rk29_i2c_goodix.c

(2)多寄存器批量读写

// I2C 批量读寄存器(reg:起始寄存器,buf:接收缓冲区,len:读取长度)

int i2c_read_regs(struct i2c_client *client, u8 reg, u8 *buf, u16 len) {    struct i2c_msg msgs[2] = {        { .addr = client->addr, .flags = 0, .len = 1, .buf = &reg },        { .addr = client->addr, .flags = I2C_M_RD, .len = len, .buf = buf },    };    int ret = i2c_transfer(client->adapter, msgs, 2);    if (ret != 2) {        dev_err(&client->dev, "I2C批量读失败,reg=0x%02x, len=%d\n", reg, len);        return ret < 0 ? ret : -EIO;    }    return 0;}// I2C 批量写寄存器int i2c_write_regs(struct i2c_client *client, u8 reg, const u8 *buf, u16 len) {    // 拼接寄存器地址 + 数据    u8 *tx_buf = devm_kzalloc(&client->dev, len + 1, GFP_KERNEL);    if (!tx_buf) return -ENOMEM;    tx_buf[0] = reg;    memcpy(&tx_buf[1], buf, len);    struct i2c_msg msg = {        .addr = client->addr,        .flags = 0,        .len = len + 1,        .buf = tx_buf,    };    int ret = i2c_transfer(client->adapter, &msg, 1);    devm_kfree(&client->dev, tx_buf);  // 释放临时缓冲区    if (ret != 1) {        dev_err(&client->dev, "I2C批量写失败,reg=0x%02x, len=%d\n", reg, len);        return ret < 0 ? ret : -EIO;    }    return 0;}

rtc/rtc-max6900.c

读取寄存器函数

写寄存器函数

(3)SMBus 简化接口(适合标准外设)

// SMBus 读字节(等价于单寄存器读)int i2c_smbus_read_byte(struct i2c_client *client, u8 reg) {    int ret = i2c_smbus_read_byte_data(client, reg);    if (ret < 0) {        dev_err(&client->dev, "SMBus读字节失败,reg=0x%02x\n", reg);        return ret;    }    return ret;  // 返回读取的字节值}// SMBus 写字节int i2c_smbus_write_byte(struct i2c_client *client, u8 reg, u8 val) {    int ret = i2c_smbus_write_byte_data(client, reg, val);    if (ret < 0) {        dev_err(&client->dev, "SMBus写字节失败,reg=0x%02x\n", reg);    }    return ret;}//其中 /** * i2c_smbus_read_byte_data - SMBus "read byte" protocol * @client: Handle to slave device * @command: Byte interpreted by slave * * This executes the SMBus "read byte" protocol, returning negative errno * else a data byte received from the device. */s32 i2c_smbus_read_byte_data(const struct i2c_client *client, u8 command){	union i2c_smbus_data data;	int status;	status = i2c_smbus_xfer(client->adapter, client->addr, client->flags,				I2C_SMBUS_READ, command,				I2C_SMBUS_BYTE_DATA, &data);return (status < 0) ? status : data.byte;}EXPORT_SYMBOL(i2c_smbus_read_byte_data);/** * i2c_smbus_write_byte_data - SMBus "write byte" protocol * @client: Handle to slave device * @command: Byte interpreted by slave * @value: Byte being written * * This executes the SMBus "write byte" protocol, returning negative errno * else zero on success. */s32 i2c_smbus_write_byte_data(const struct i2c_client *client, u8 command,			      u8 value){	union i2c_smbus_data data;data.byte = value;return i2c_smbus_xfer(client->adapter, client->addr, client->flags,			      I2C_SMBUS_WRITE, command,			      I2C_SMBUS_BYTE_DATA, &data);}EXPORT_SYMBOL(i2c_smbus_write_byte_data);
其中i2c_smbus_read_byte_data用于通过 I2C SMBus 协议,从指定 I2C 设备(由 i2c_client 指定)的某个寄存器(由 command 参数指定)读取1 个字节的数据。
client:指向 struct i2c_client 的指针,代表一个具体的 I2C 设备(包含设备地址、挂载的 I2C 适配器等信息);
command:要读取的寄存器地址(或命令码),对应 I2C 设备的子地址;
返回值:成功返回读取到的字节数据(0~255),失败返回负数错误码(如 -EIO 表示通信失败)。
都调用的i2c_smbus_xfer,这个函数主要做 “基础校验 + 转发”
status = i2c_smbus_xfer(    client->adapter,    // 参数1:I2C适配器(对应硬件I2C控制器,如i2c-0)    client->addr,       // 参数2:目标I2C设备的7/10位地址    client->flags,      // 参数3:通信标志(如I2C_M_TEN表示10位地址、I2C_M_RECV_LEN等)    I2C_SMBUS_READ,     // 参数4:操作类型——读    command,            // 参数5:寄存器地址/命令码(要读的哪个寄存器)    I2C_SMBUS_BYTE_DATA,// 参数6:数据类型——单字节数据(寄存器+1字节)    &data// 参数7:输出缓冲区——存储读取到的字节数据);

函数返回值 status 的含义:

  • 成功:返回0(注意:读取到的实际数据存在data.byte中,不是返回值);
  • 失败:返回负数错误码(如-EIO表示通信失败、-ETIMEDOUT表示超时、-EINVAL表示参数非法)。
最后调用
static s32 i2c_smbus_xfer_emulated(struct i2c_adapter *adapter, u16 addr,				   unsigned short flags,				   char read_write, u8 command, int size,				   union i2c_smbus_data *data)				   {......	status = i2c_transfer(adapter, msg, num);if (status < 0)return status;.........		}
i2c_smbus_read_byte_data是高层业务接口,封装了 “读单字节寄存器” 的语义,无需关心协议细节;
i2c_smbus_xfer是中间桥接层,仅做参数校验和重试,核心逻辑交给 __i2c_smbus_xfer;
__i2c_smbus_xfer是协议转换核心,将 SMBus 操作转换为 I2C 标准消息序列,是整个流程的关键节点。
i2c_smbus_xfer → __i2c_smbus_xfer(构造 i2c_msg) → i2c_transfer;
小结一下:
追踪代码可以发现
i2c_read_reg(struct i2c_client *client, u8 reg, u8 *val)int i2c_read_regs(struct i2c_client *client, u8 reg, u8 *buf, u16 len) int i2c_smbus_read_byte(struct i2c_client *client, u8 reg)
最后都调用 
i2c_transfer(structi2c_adapter*adap,structi2c_msg*msgs,int num)
  1. 看一下i2c_transfer最后执行到哪里?

int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num) 

if (in_atomic() || irqs_disabled()) {//中断关闭			ret = i2c_trylock_adapter(adap);if (!ret)/* I2C activity is ongoing. */return -EAGAIN;		} else {			i2c_lock_adapter(adap); // 2. 加总线锁(防止并发访问I2C总线)		}		ret = __i2c_transfer(adap, msgs, num);//3.适配器底层的xfer函数(真正的硬件操作入口)		i2c_unlock_adapter(adap); // 4. 释放总线锁return ret;	} else {		dev_dbg(&adap->dev, "I2C level transfers not supported\n");return -EOPNOTSUPP;	}
  其中
int __i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num){unsigned long orig_jiffies;int ret, try;/* Retry automatically on arbitration loss */	orig_jiffies = jiffies;for (ret = 0, try = 0; try <= adap->retries; try++) {		ret = adap->algo->master_xfer(adap, msgs, num);if (ret != -EAGAIN)break;if (time_after(jiffies, orig_jiffies + adap->timeout))break;	}return ret;}EXPORT_SYMBOL(__i2c_transfer);
adap->algo->master_xfer(adap, msgs, num)是具体 I2C 控制器驱动实现的硬件操作函数(比如i2c-imx.c、i2c-gpio.c中的xfer),它会直接操作硬件寄存器,按照i2c_msg消息序列控制 SCL/SDA 时序,完成实际的读写 —— 这是i2c_transfer的最终落地,也是整个调用链路的终点。
  • i2c_transfer
     是 I2C 消息执行的统一入口,核心作用是参数校验、总线加锁、转发消息到适配器底层;
  • 它不直接操作硬件,而是调用适配器 algo->xfer 函数(硬件相关),实现 “上层统一接口 + 底层差异化实现”;
  • 关键机制:总线锁(防抢占)、重试机制(提稳定性)、消息序列执行(支持多步操作)。

好的I2C的读写就到这里,接下来是SPI 读写。

三、SPI 读写函数总结

1. 核心 API 分类

SPI 无统一的「消息传输」函数,需根据内核版本选择:

内核版本
核心函数
适用场景
返回值说明
3.15+(推荐)
spi_transfer()
 + spi_message
任意 SPI 读写(支持多段传输)
成功:0;失败:负数错误码
旧版本
spi_sync()
简化单段传输
成功:0;失败:负数
简化接口
spi_write/spi_read
纯写 / 纯读(无片选控制)
成功:0;失败:负数

2. 通用读写模板(3.15+ 内核)

(1)单寄存器读写(SPI 通常用指令 + 数据格式)

#include<linux/spi/spi.h>// SPI 读单寄存器(cmd:读写指令,val:接收值)intspi_read_reg(struct spi_device *spi, u8 cmd, u8 *val){    u8 tx_buf[2] = {cmd | 0x80, 0x00};  // 读指令(最高位1表示读)    u8 rx_buf[2] = {0};    // 组装SPI传输段    struct spi_transfer t = {        .tx_buf = tx_buf,        // 发送缓冲区(指令)        .rx_buf = rx_buf,        // 接收缓冲区(数据)        .len = ARRAY_SIZE(tx_buf), // 传输长度        .speed_hz = spi->max_speed_hz, // 传输速率(可用自定义值)        .cs_change = 0,          // 传输后不释放片选    };    // 组装SPI消息    struct spi_message msg;    spi_message_init(&msg);    spi_message_add_tail(&t, &msg);    // 执行传输    int ret = spi_sync(spi, &msg);    if (ret < 0) {        dev_err(&spi->dev, "SPI读寄存器0x%02x失败,ret=%d\n", cmd, ret);        return ret;    }    *val = rx_buf[1];  // 提取读取的值(根据外设协议调整)    return 0;}// SPI 写单寄存器intspi_write_reg(struct spi_device *spi, u8 cmd, u8 val){    u8 tx_buf[2] = {cmd & 0x7F, val};  // 写指令(最高位0表示写)    struct spi_transfer t = {        .tx_buf = tx_buf,        .len = ARRAY_SIZE(tx_buf),        .speed_hz = spi->max_speed_hz,        .cs_change = 0,    };    struct spi_message msg;    spi_message_init(&msg);    spi_message_add_tail(&t, &msg);    int ret = spi_sync(spi, &msg);    if (ret < 0) {        dev_err(&spi->dev, "SPI写寄存器0x%02x失败,ret=%d\n", cmd, ret);    }    return ret;}

(2)多字节批量读写

// SPI 批量读(cmd:起始指令,buf:接收缓冲区,len:读取长度)int spi_read_bulk(struct spi_device *spi, u8 cmd, u8 *buf, u16 len) {    // 分配发送缓冲区(指令 + 占位符)    u8 *tx_buf = devm_kzalloc(&spi->dev, len + 1, GFP_KERNEL);    if (!tx_buf) return -ENOMEM;    tx_buf[0] = cmd | 0x80;  // 读指令    struct spi_transfer t = {        .tx_buf = tx_buf,        .rx_buf = buf,        .len = len + 1,        // 指令1字节 + 数据len字节        .speed_hz = spi->max_speed_hz,    };    struct spi_message msg;    spi_message_init(&msg);    spi_message_add_tail(&t, &msg);    int ret = spi_sync(spi, &msg);    devm_kfree(&spi->dev, tx_buf);    if (ret < 0) {        dev_err(&spi->dev, "SPI批量读失败,cmd=0x%02x, len=%d\n", cmd, len);        return ret;    }    // 注意:buf[0]是指令回显,有效数据从buf[1]开始(根据外设调整)    memmove(buf, &buf[1], len);    return 0;}// SPI 批量写int spi_write_bulk(struct spi_device *spi, u8 cmd, const u8 *buf, u16 len) {    u8 *tx_buf = devm_kzalloc(&spi->dev, len + 1, GFP_KERNEL);    if (!tx_buf) return -ENOMEM;    tx_buf[0] = cmd & 0x7F;  // 写指令    memcpy(&tx_buf[1], buf, len);    struct spi_transfer t = {        .tx_buf = tx_buf,        .len = len + 1,        .speed_hz = spi->max_speed_hz,    };    struct spi_message msg;    spi_message_init(&msg);    spi_message_add_tail(&t, &msg);    int ret = spi_sync(spi, &msg);    devm_kfree(&spi->dev, tx_buf);    if (ret < 0) {        dev_err(&spi->dev, "SPI批量写失败,cmd=0x%02x, len=%d\n", cmd, len);    }    return ret;}

(3)简化接口(纯写 / 纯读)

// 纯写(无寄存器地址,直接发数据)int spi_write_simple(struct spi_device *spi, const u8 *buf, u16 len) {    return spi_write(spi, buf, len);  // 封装好的简化函数}// 纯读(先发送dummy数据,再读)int spi_read_simple(struct spi_device *spi, u8 *buf, u16 len) {    // 分配dummy发送缓冲区    u8 *tx_buf = devm_kzalloc(&spi->dev, len, GFP_KERNEL);    if (!tx_buf) return -ENOMEM;    struct spi_transfer t = {        .tx_buf = tx_buf,        .rx_buf = buf,        .len = len,    };    struct spi_message msg;    spi_message_init(&msg);    spi_message_add_tail(&t, &msg);    int ret = spi_sync(spi, &msg);    devm_kfree(&spi->dev, tx_buf);    return ret;}

四、SPI vs I2C 读写核心差异

维度
I2C 读写
SPI 读写
地址方式
7/10 位外设地址(client->addr)
片选(CS)引脚区分外设,无总线地址
读写标志
I2C_M_RD
 标志区分读写
指令位(如最高位)区分读写
传输单位
i2c_msg
 消息(可多段)
spi_transfer
 传输段 + spi_message
片选控制
无片选,靠地址应答
cs_change
 控制片选释放 / 保持
速率控制
设备树 clock-frequency
speed_hz
 字段自定义
错误码
-EIO
(无应答)、-ENXIO(无总线)
-EIO
(传输失败)、-EPROTO(协议错)

五、通用避坑要点

1. 硬件相关

  • 地址校验:I2C 外设地址是 7 位(不要带读写位),SPI 无地址但要确认 CS 引脚;
  • 速率匹配:传输速率不能超过外设最大支持值(I2C 常见 100K/400K,SPI 常见 1M/10M);
  • 时序匹配:SPI 需确认 CPOL/CPHA(时钟极性 / 相位),I2C 需确认上拉电阻。

2. 软件相关

  • 返回值检查:必须检查所有读写函数的返回值,不能忽略 -EIO 等错误;
  • 缓冲区对齐:SPI 传输缓冲区建议用 kmalloc(DMA 兼容),避免栈上缓冲区;
  • 上下文约束:中断上下文禁止用 devm_kzalloc(GFP_KERNEL),需用 GFP_ATOMIC;
  • 数据长度:I2C i2c_transfer 的 len 不能超过适配器最大长度(通常 4096)。

3. 调试技巧

  • 先用户层验证:I2C 用 i2cget/i2cset,SPI 用 spidev_test 工具验证硬件连通性;
  • 开启调试日志:
# I2C 调试日志echo 7 > /sys/module/i2c_core/parameters/debug# SPI 调试日志echo 1 > /sys/module/spi_core/parameters/debug

抓波形验证:用逻辑分析仪对比读写时序是否符合外设手册。

最后总结

  1. I2C 核心
    以 i2c_transfer 为核心,通过 i2c_msg 组装「地址 + 数据」,适合多外设共享总线;
  2. SPI 核心
    以 spi_sync + spi_transfer 为核心,通过指令位区分读写,适合高速传输;
  3. 通用模板
    单寄存器读写是基础,批量读写需注意缓冲区拼接和数据偏移;
  4. 避坑关键
    校验硬件参数、检查返回值、匹配外设时序,先工具验证再写驱动。

谢谢阅读点赞收藏!

最新文章

随机文章