大家好,我是王鸽,这篇主要是对 Linux 驱动下的SPI 和I2C 读写函数具体介绍一下,因为之前的文章都是对这两个通讯协议框架流程解读,怎么读写发送数据没有详细解释。一、核心前提:SPI/I2C 驱动的通用结构
无论是 SPI 还是 I2C,驱动读写的核心逻辑都是:
| | | |
|---|
| i2c_transfer() | | |
| i2c_smbus_read/write_*() | | |
| 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 = ®, // 寄存器地址缓冲区 }, // 消息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 = ® }, { .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)
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 无统一的「消息传输」函数,需根据内核版本选择:
| | | |
|---|
| spi_transfer() | | |
| spi_sync() | | |
| spi_write/spi_read | | |
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_M_RD | |
| i2c_msg | spi_transfer |
| | cs_change |
| | speed_hz |
| -EIO | -EIO |
五、通用避坑要点
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
抓波形验证:用逻辑分析仪对比读写时序是否符合外设手册。
最后总结
- I2C 核心以
i2c_transfer 为核心,通过 i2c_msg 组装「地址 + 数据」,适合多外设共享总线; - SPI 核心以
spi_sync + spi_transfer 为核心,通过指令位区分读写,适合高速传输; - 通用模板单寄存器读写是基础,批量读写需注意缓冲区拼接和数据偏移;
- 避坑关键校验硬件参数、检查返回值、匹配外设时序,先工具验证再写驱动。
谢谢阅读点赞收藏!