《Linux 设备驱动开发详解:基于最新的 Linux 4.0 内核》
第 15 章 Linux I2C 核心、总线与设备驱动
参考:宋宝华 著,机械工业出版社,2015年版
15.1 Linux I2C 体系结构
15.1.1 I2C 总线回顾
I2C(Inter-Integrated Circuit)是由 Philips(现 NXP)公司开发的两线制同步串行总线,只需 SDA(数据线)和 SCL(时钟线)即可连接多个设备:
I2C 总线物理连接:
VCC
│
├── 上拉电阻(4.7kΩ)── SDA ──┬──────────────┬──────────────┐
│ │ │ │
└── 上拉电阻(4.7kΩ)── SCL ──┼──────────────┼──────────────┤
│ │ │
主设备 从设备0 从设备1
(Master) (Addr:0x48) (Addr:0x50)
SoC I2C控制器 LM75温度传感器 AT24C02 EEPROM
I2C 总线特性:
- 只需 2 根信号线(SDA + SCL)
- 支持多主多从(通过仲裁机制)
- 每个从设备有唯一的 7 位或 10 位地址
- 标准模式:100kbps;快速模式:400kbps;高速模式:3.4Mbps
- 开漏输出 + 上拉电阻(允许多设备共享总线)
15.1.2 Linux I2C 体系结构概述
Linux 将 I2C 驱动分为三个层次,形成清晰的分层架构:
Linux I2C 体系结构:
┌─────────────────────────────────────────────────────────────┐
│ 用户空间 │
│ 应用程序通过 /dev/i2c-N 或设备驱动接口访问 I2C 设备 │
└──────────────────────────┬──────────────────────────────────┘
│
┌──────────────────────────▼──────────────────────────────────┐
│ I2C 设备驱动层 │
│ (针对具体 I2C 设备的驱动,如温度传感器、EEPROM 等) │
│ i2c_driver + i2c_client │
│ 实现:probe/remove/read/write 等操作 │
└──────────────────────────┬──────────────────────────────────┘
│ i2c_transfer() / i2c_smbus_xxx()
┌──────────────────────────▼──────────────────────────────────┐
│ I2C 核心层(I2C Core) │
│ 提供统一的 API,管理适配器和设备的注册/注销 │
│ i2c_add_adapter / i2c_register_driver │
│ i2c_transfer / i2c_smbus_read_byte_data 等 │
└──────────────────────────┬──────────────────────────────────┘
│ master_xfer()
┌──────────────────────────▼──────────────────────────────────┐
│ I2C 适配器驱动层 │
│ (针对具体 SoC 的 I2C 控制器驱动) │
│ i2c_adapter + i2c_algorithm │
│ 实现:master_xfer(实际的 I2C 时序操作) │
└──────────────────────────┬──────────────────────────────────┘
│ 操作硬件寄存器
┌──────────────────────────▼──────────────────────────────────┐
│ I2C 硬件层 │
│ SoC 内置 I2C 控制器(如 i.MX6 的 I2C1/I2C2/I2C3) │
│ 外部 I2C 设备(LM75、AT24C02、MPU6050 等) │
└─────────────────────────────────────────────────────────────┘
15.1.3 I2C 体系结构的三个核心概念
三个核心概念:
1. i2c_adapter(I2C 适配器)
对应 SoC 上的一个 I2C 控制器
每个 I2C 总线对应一个 adapter
例如:i2c-0(/dev/i2c-0)、i2c-1(/dev/i2c-1)
2. i2c_client(I2C 客户端/设备)
对应 I2C 总线上的一个从设备
包含设备地址、所属 adapter 等信息
例如:地址 0x48 的 LM75 温度传感器
3. i2c_driver(I2C 驱动)
对应一类 I2C 设备的驱动程序
通过 id_table 或 of_match_table 与 i2c_client 匹配
实现 probe/remove 等函数
三者的关系:
i2c_adapter(总线)
├── i2c_client(设备0,地址0x48)← i2c_driver(LM75驱动)
├── i2c_client(设备1,地址0x50)← i2c_driver(AT24C02驱动)
└── i2c_client(设备2,地址0x68)← i2c_driver(MPU6050驱动)
15.2 Linux I2C 核心
15.2.1 I2C 核心的作用
I2C 核心(I2C Core)是 Linux I2C 子系统的中间层,提供:
- 适配器管理:注册/注销 I2C 适配器
- 设备管理:注册/注销 I2C 设备
- 驱动管理:注册/注销 I2C 驱动,完成设备与驱动的匹配
- 通信 API:提供统一的 I2C 传输接口
15.2.2 i2c_adapter ------ I2C 适配器
c
#include <linux/i2c.h>
/*
* i2c_adapter:描述一个 I2C 总线控制器
* 每个 SoC 的 I2C 控制器对应一个 i2c_adapter
*/
struct i2c_adapter {
struct module *owner;
unsigned int class; /* 适配器类型 */
const struct i2c_algorithm *algo; /* 算法(实现实际传输)*/
void *algo_data; /* 算法私有数据 */
int nr; /* 适配器编号(/dev/i2c-N 中的 N)*/
char name[48]; /* 适配器名称 */
struct device dev; /* 内嵌设备结构体 */
/* ... */
};
/*
* i2c_algorithm:I2C 传输算法
* 适配器驱动必须实现 master_xfer 函数
*/
struct i2c_algorithm {
/*
* master_xfer:执行 I2C 传输
* adap:适配器
* msgs:消息数组
* num:消息数量
* 返回:成功传输的消息数,负值表示错误
*/
int (*master_xfer)(struct i2c_adapter *adap,
struct i2c_msg *msgs, int num);
/* 功能查询(返回适配器支持的功能标志)*/
u32 (*functionality)(struct i2c_adapter *adap);
/* SMBus 传输(可选,不实现则由 master_xfer 模拟)*/
int (*smbus_xfer)(struct i2c_adapter *adap, u16 addr,
unsigned short flags, char read_write,
u8 command, int size, union i2c_smbus_data *data);
};
15.2.3 i2c_msg ------ I2C 消息
c
/*
* i2c_msg:描述一次 I2C 传输消息
* 一次完整的 I2C 传输可能包含多个消息(如写地址后读数据)
*/
struct i2c_msg {
__u16 addr; /* 从设备地址(7位或10位)*/
__u16 flags; /* 传输标志 */
__u16 len; /* 数据长度(字节)*/
__u8 *buf; /* 数据缓冲区 */
};
/* flags 常用值 */
#define I2C_M_RD 0x0001 /* 读操作(从设备→主设备)*/
#define I2C_M_TEN 0x0010 /* 10位地址 */
#define I2C_M_NOSTART 0x4000 /* 不发送 START 条件 */
#define I2C_M_STOP 0x8000 /* 强制发送 STOP 条件 */
15.2.4 I2C 核心 API
c
/* ── 适配器管理 ──────────────────────────────────────────── */
/* 注册 I2C 适配器(动态分配编号)*/
int i2c_add_adapter(struct i2c_adapter *adap);
/* 注册 I2C 适配器(指定编号)*/
int i2c_add_numbered_adapter(struct i2c_adapter *adap);
/* 注销 I2C 适配器 */
void i2c_del_adapter(struct i2c_adapter *adap);
/* ── 驱动管理 ──────────────────────────────────────────── */
/* 注册 I2C 驱动 */
int i2c_register_driver(struct module *owner, struct i2c_driver *driver);
/* 简化宏 */
#define i2c_add_driver(driver) \
i2c_register_driver(THIS_MODULE, driver)
/* 注销 I2C 驱动 */
void i2c_del_driver(struct i2c_driver *driver);
/* 简化宏 */
#define module_i2c_driver(__i2c_driver) \
module_driver(__i2c_driver, i2c_add_driver, i2c_del_driver)
/* ── 数据传输 ──────────────────────────────────────────── */
/*
* i2c_transfer:执行 I2C 传输(最底层接口)
* adap:适配器
* msgs:消息数组
* num:消息数量
* 返回:成功传输的消息数,负值表示错误
*/
int i2c_transfer(struct i2c_adapter *adap,
struct i2c_msg *msgs, int num);
/*
* i2c_master_send:向从设备发送数据
* client:I2C 客户端
* buf:数据缓冲区
* count:字节数
*/
int i2c_master_send(const struct i2c_client *client,
const char *buf, int count);
/*
* i2c_master_recv:从从设备接收数据
*/
int i2c_master_recv(const struct i2c_client *client,
char *buf, int count);
/* ── SMBus 接口(更高层,基于 i2c_transfer 实现)──────── */
/* 读取一个字节 */
s32 i2c_smbus_read_byte(const struct i2c_client *client);
/* 写入一个字节 */
s32 i2c_smbus_write_byte(const struct i2c_client *client, u8 value);
/* 读取指定寄存器的一个字节 */
s32 i2c_smbus_read_byte_data(const struct i2c_client *client, u8 command);
/* 向指定寄存器写入一个字节 */
s32 i2c_smbus_write_byte_data(const struct i2c_client *client,
u8 command, u8 value);
/* 读取指定寄存器的两个字节(16位)*/
s32 i2c_smbus_read_word_data(const struct i2c_client *client, u8 command);
/* 向指定寄存器写入两个字节 */
s32 i2c_smbus_write_word_data(const struct i2c_client *client,
u8 command, u16 value);
/* 读取多个字节(块读)*/
s32 i2c_smbus_read_i2c_block_data(const struct i2c_client *client,
u8 command, u8 length, u8 *values);
/* 写入多个字节(块写)*/
s32 i2c_smbus_write_i2c_block_data(const struct i2c_client *client,
u8 command, u8 length,
const u8 *values);
/* 检查适配器是否支持某功能 */
int i2c_check_functionality(struct i2c_adapter *adap, u32 func);
15.2.5 i2c_transfer 的使用案例
c
/*
* 案例:使用 i2c_transfer 读取 AT24C02 EEPROM
* AT24C02 读操作:
* 1. 发送写命令(设备地址 + W)+ 字节地址
* 2. 发送读命令(设备地址 + R)+ 读取数据
*/
static int at24c02_read(struct i2c_client *client,
u8 reg_addr, u8 *data, int len)
{
struct i2c_msg msgs[2];
int ret;
/* 消息1:写字节地址(告诉 EEPROM 从哪里开始读)*/
msgs[0].addr = client->addr; /* 设备地址(如 0x50)*/
msgs[0].flags = 0; /* 写操作(无 I2C_M_RD 标志)*/
msgs[0].len = 1; /* 发送1字节(寄存器地址)*/
msgs[0].buf = ®_addr; /* 寄存器地址 */
/* 消息2:读数据(重复 START + 读命令)*/
msgs[1].addr = client->addr;
msgs[1].flags = I2C_M_RD; /* 读操作 */
msgs[1].len = len; /* 读取字节数 */
msgs[1].buf = data; /* 接收缓冲区 */
/* 执行传输(两个消息,中间有 Repeated START)*/
ret = i2c_transfer(client->adapter, msgs, 2);
if (ret != 2) {
dev_err(&client->dev, "I2C 读取失败:%d\n", ret);
return ret < 0 ? ret : -EIO;
}
return 0;
}
/*
* 案例:使用 i2c_smbus_xxx 读取 LM75 温度传感器
* LM75 温度寄存器地址:0x00
*/
static int lm75_read_temp(struct i2c_client *client, int *temp_mc)
{
s32 ret;
/* 读取温度寄存器(16位)*/
ret = i2c_smbus_read_word_data(client, 0x00);
if (ret < 0) {
dev_err(&client->dev, "读取温度失败:%d\n", ret);
return ret;
}
/*
* LM75 温度数据格式:
* 高字节:温度整数部分(有符号)
* 低字节:bit7 = 0.5°C 分辨率
* 字节序:大端(需要交换)
*/
s16 raw = (s16)swab16((u16)ret);
raw >>= 5; /* 右移5位,取高11位 */
/* 转换为毫摄氏度 */
*temp_mc = (raw / 8) * 1000 + (raw % 8) * 125;
return 0;
}
15.3 Linux I2C 适配器驱动
15.3.1 适配器驱动的作用
I2C 适配器驱动负责控制 SoC 内置的 I2C 控制器硬件,实现实际的 I2C 时序操作。每个 SoC 厂商都需要为自己的 I2C 控制器编写适配器驱动。
15.3.2 适配器驱动的框架
c
/*
* i2c_adapter_driver.c ------ I2C 适配器驱动框架
* 以简化的 I2C 控制器为例
*/
#include <linux/module.h>
#include <linux/i2c.h>
#include <linux/platform_device.h>
#include <linux/clk.h>
#include <linux/io.h>
#include <linux/interrupt.h>
#include <linux/completion.h>
/* I2C 控制器寄存器定义 */
#define I2C_CTRL_REG 0x00 /* 控制寄存器 */
#define I2C_STATUS_REG 0x04 /* 状态寄存器 */
#define I2C_DATA_REG 0x08 /* 数据寄存器 */
#define I2C_ADDR_REG 0x0C /* 地址寄存器 */
#define I2C_CLK_REG 0x10 /* 时钟分频寄存器 */
/* 控制寄存器位定义 */
#define I2C_CTRL_EN BIT(0) /* 使能 I2C 控制器 */
#define I2C_CTRL_START BIT(1) /* 发送 START 条件 */
#define I2C_CTRL_STOP BIT(2) /* 发送 STOP 条件 */
#define I2C_CTRL_READ BIT(3) /* 读操作 */
#define I2C_CTRL_ACK BIT(4) /* 发送 ACK */
#define I2C_CTRL_INTEN BIT(5) /* 使能中断 */
/* 状态寄存器位定义 */
#define I2C_STATUS_BUSY BIT(0) /* 总线忙 */
#define I2C_STATUS_DONE BIT(1) /* 传输完成 */
#define I2C_STATUS_NACK BIT(2) /* 收到 NACK */
#define I2C_STATUS_ARB BIT(3) /* 仲裁失败 */
/* 适配器私有数据 */
struct my_i2c_dev {
struct i2c_adapter adapter; /* 内嵌 i2c_adapter */
void __iomem *base; /* 寄存器基地址 */
int irq; /* 中断号 */
struct clk *clk; /* 时钟 */
struct completion completion; /* 传输完成信号 */
int msg_err; /* 传输错误码 */
spinlock_t lock;
};
/* ── 中断处理函数 ──────────────────────────────────────────── */
static irqreturn_t my_i2c_irq_handler(int irq, void *dev_id)
{
struct my_i2c_dev *i2c_dev = dev_id;
u32 status = readl(i2c_dev->base + I2C_STATUS_REG);
/* 清除中断 */
writel(status, i2c_dev->base + I2C_STATUS_REG);
if (status & I2C_STATUS_NACK) {
i2c_dev->msg_err = -ENXIO; /* 设备无应答 */
} else if (status & I2C_STATUS_ARB) {
i2c_dev->msg_err = -EAGAIN; /* 仲裁失败 */
} else if (status & I2C_STATUS_DONE) {
i2c_dev->msg_err = 0; /* 传输成功 */
}
/* 通知等待的传输函数 */
complete(&i2c_dev->completion);
return IRQ_HANDLED;
}
/* ── 发送 START 条件 ──────────────────────────────────────── */
static int my_i2c_start(struct my_i2c_dev *i2c_dev,
u8 addr, int read)
{
u32 ctrl;
unsigned long timeout;
/* 等待总线空闲 */
timeout = jiffies + msecs_to_jiffies(100);
while (readl(i2c_dev->base + I2C_STATUS_REG) & I2C_STATUS_BUSY) {
if (time_after(jiffies, timeout))
return -ETIMEDOUT;
msleep(1);
}
/* 设置从设备地址 */
writel((addr << 1) | (read ? 1 : 0),
i2c_dev->base + I2C_ADDR_REG);
/* 发送 START 条件 */
reinit_completion(&i2c_dev->completion);
ctrl = I2C_CTRL_EN | I2C_CTRL_START | I2C_CTRL_INTEN;
if (read)
ctrl |= I2C_CTRL_READ;
writel(ctrl, i2c_dev->base + I2C_CTRL_REG);
/* 等待传输完成 */
if (!wait_for_completion_timeout(&i2c_dev->completion,
msecs_to_jiffies(100)))
return -ETIMEDOUT;
return i2c_dev->msg_err;
}
/* ── 核心传输函数(master_xfer)──────────────────────────── */
static int my_i2c_master_xfer(struct i2c_adapter *adap,
struct i2c_msg *msgs, int num)
{
struct my_i2c_dev *i2c_dev = i2c_get_adapdata(adap);
int i, j, ret;
for (i = 0; i < num; i++) {
struct i2c_msg *msg = &msgs[i];
int read = (msg->flags & I2C_M_RD) ? 1 : 0;
/* 发送 START + 地址 */
ret = my_i2c_start(i2c_dev, msg->addr, read);
if (ret) {
dev_err(&adap->dev, "START 失败:%d\n", ret);
goto out_stop;
}
/* 传输数据 */
for (j = 0; j < msg->len; j++) {
reinit_completion(&i2c_dev->completion);
if (read) {
/* 读操作:触发读取 */
u32 ctrl = I2C_CTRL_EN | I2C_CTRL_READ | I2C_CTRL_INTEN;
/* 最后一个字节发送 NACK */
if (j == msg->len - 1)
ctrl &= ~I2C_CTRL_ACK;
else
ctrl |= I2C_CTRL_ACK;
writel(ctrl, i2c_dev->base + I2C_CTRL_REG);
/* 等待完成 */
if (!wait_for_completion_timeout(&i2c_dev->completion,
msecs_to_jiffies(100))) {
ret = -ETIMEDOUT;
goto out_stop;
}
/* 读取数据 */
msg->buf[j] = readl(i2c_dev->base + I2C_DATA_REG) & 0xFF;
} else {
/* 写操作:写入数据并触发发送 */
writel(msg->buf[j], i2c_dev->base + I2C_DATA_REG);
writel(I2C_CTRL_EN | I2C_CTRL_INTEN,
i2c_dev->base + I2C_CTRL_REG);
/* 等待完成 */
if (!wait_for_completion_timeout(&i2c_dev->completion,
msecs_to_jiffies(100))) {
ret = -ETIMEDOUT;
goto out_stop;
}
if (i2c_dev->msg_err) {
ret = i2c_dev->msg_err;
goto out_stop;
}
}
}
}
ret = num; /* 成功传输的消息数 */
out_stop:
/* 发送 STOP 条件 */
writel(I2C_CTRL_EN | I2C_CTRL_STOP, i2c_dev->base + I2C_CTRL_REG);
udelay(10);
return ret;
}
/* ── 功能查询函数 ──────────────────────────────────────────── */
static u32 my_i2c_functionality(struct i2c_adapter *adap)
{
return I2C_FUNC_I2C |
I2C_FUNC_SMBUS_EMUL | /* 通过 master_xfer 模拟 SMBus */
I2C_FUNC_10BIT_ADDR; /* 支持10位地址 */
}
/* ── I2C 算法 ──────────────────────────────────────────────── */
static const struct i2c_algorithm my_i2c_algorithm = {
.master_xfer = my_i2c_master_xfer,
.functionality = my_i2c_functionality,
};
/* ── probe 函数 ──────────────────────────────────────────────── */
static int my_i2c_probe(struct platform_device *pdev)
{
struct my_i2c_dev *i2c_dev;
struct resource *res;
int ret;
/* 分配私有数据 */
i2c_dev = devm_kzalloc(&pdev->dev, sizeof(*i2c_dev), GFP_KERNEL);
if (!i2c_dev)
return -ENOMEM;
/* 获取并映射寄存器 */
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
i2c_dev->base = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(i2c_dev->base))
return PTR_ERR(i2c_dev->base);
/* 获取中断号 */
i2c_dev->irq = platform_get_irq(pdev, 0);
if (i2c_dev->irq < 0)
return i2c_dev->irq;
/* 申请中断 */
ret = devm_request_irq(&pdev->dev, i2c_dev->irq,
my_i2c_irq_handler, 0, "my-i2c", i2c_dev);
if (ret)
return ret;
/* 获取并使能时钟 */
i2c_dev->clk = devm_clk_get(&pdev->dev, NULL);
if (IS_ERR(i2c_dev->clk))
return PTR_ERR(i2c_dev->clk);
clk_prepare_enable(i2c_dev->clk);
/* 初始化完成量和自旋锁 */
init_completion(&i2c_dev->completion);
spin_lock_init(&i2c_dev->lock);
/* 初始化 I2C 适配器 */
i2c_dev->adapter.owner = THIS_MODULE;
i2c_dev->adapter.algo = &my_i2c_algorithm;
i2c_dev->adapter.dev.parent = &pdev->dev;
i2c_dev->adapter.dev.of_node = pdev->dev.of_node;
snprintf(i2c_dev->adapter.name, sizeof(i2c_dev->adapter.name),
"my-i2c");
/* 设置适配器私有数据 */
i2c_set_adapdata(&i2c_dev->adapter, i2c_dev);
/* 配置 I2C 时钟(100kHz)*/
u32 clk_rate = clk_get_rate(i2c_dev->clk);
writel(clk_rate / (100000 * 2), i2c_dev->base + I2C_CLK_REG);
/* 使能 I2C 控制器 */
writel(I2C_CTRL_EN, i2c_dev->base + I2C_CTRL_REG);
/* 注册 I2C 适配器 */
ret = i2c_add_adapter(&i2c_dev->adapter);
if (ret) {
dev_err(&pdev->dev, "注册 I2C 适配器失败:%d\n", ret);
clk_disable_unprepare(i2c_dev->clk);
return ret;
}
platform_set_drvdata(pdev, i2c_dev);
dev_info(&pdev->dev, "I2C 适配器注册成功,编号 %d\n",
i2c_dev->adapter.nr);
return 0;
}
static int my_i2c_remove(struct platform_device *pdev)
{
struct my_i2c_dev *i2c_dev = platform_get_drvdata(pdev);
i2c_del_adapter(&i2c_dev->adapter);
clk_disable_unprepare(i2c_dev->clk);
return 0;
}
static const struct of_device_id my_i2c_of_match[] = {
{ .compatible = "myvendor,my-i2c" },
{}
};
MODULE_DEVICE_TABLE(of, my_i2c_of_match);
static struct platform_driver my_i2c_driver = {
.probe = my_i2c_probe,
.remove = my_i2c_remove,
.driver = {
.name = "my-i2c",
.of_match_table = my_i2c_of_match,
},
};
module_platform_driver(my_i2c_driver);
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("My I2C 适配器驱动");
15.4 Linux I2C 设备驱动
15.4.1 i2c_driver 和 i2c_client
c
/*
* i2c_driver:I2C 设备驱动
* 类似 platform_driver,通过 id_table 或 of_match_table 匹配设备
*/
struct i2c_driver {
unsigned int class;
/* 设备与驱动匹配时调用 */
int (*probe)(struct i2c_client *client,
const struct i2c_device_id *id);
/* 设备移除时调用 */
int (*remove)(struct i2c_client *client);
/* 设备 ID 表(用于非设备树匹配)*/
const struct i2c_device_id *id_table;
struct device_driver driver;
};
/*
* i2c_client:I2C 从设备
* 由 I2C 核心根据设备树或 board 文件创建
*/
struct i2c_client {
unsigned short flags; /* 标志(I2C_CLIENT_TEN 等)*/
unsigned short addr; /* 从设备地址(7位或10位)*/
char name[I2C_NAME_SIZE]; /* 设备名称 */
struct i2c_adapter *adapter; /* 所属适配器 */
struct device dev; /* 内嵌设备结构体 */
int irq; /* 中断号 */
/* ... */
};
15.4.2 完整的 I2C 设备驱动案例(LM75 温度传感器)
c
/*
* lm75.c ------ LM75 温度传感器 I2C 设备驱动
*
* LM75 是一款常见的 I2C 温度传感器:
* - I2C 地址:0x48~0x4F(由 A2A1A0 引脚决定)
* - 温度范围:-55°C ~ +125°C
* - 分辨率:0.5°C(9位)
* - 温度寄存器:0x00(只读,16位)
* - 配置寄存器:0x01(读写,8位)
* - 过温阈值寄存器:0x03(读写,16位)
* - 滞后寄存器:0x02(读写,16位)
*/
#include <linux/module.h>
#include <linux/i2c.h>
#include <linux/hwmon.h>
#include <linux/hwmon-sysfs.h>
#include <linux/slab.h>
#include <linux/err.h>
/* LM75 寄存器地址 */
#define LM75_REG_TEMP 0x00 /* 温度寄存器 */
#define LM75_REG_CONF 0x01 /* 配置寄存器 */
#define LM75_REG_THYST 0x02 /* 滞后寄存器 */
#define LM75_REG_TOS 0x03 /* 过温阈值寄存器 */
/* 驱动私有数据 */
struct lm75_data {
struct i2c_client *client;
struct device *hwmon_dev;
struct mutex update_lock;
bool valid; /* 缓存是否有效 */
unsigned long last_updated; /* 上次更新时间 */
/* 缓存的传感器数据 */
s16 temp[3]; /* [0]=当前温度, [1]=滞后, [2]=过温 */
};
/* ── 读取温度寄存器 ──────────────────────────────────────── */
static s16 lm75_read_reg(struct i2c_client *client, u8 reg)
{
s32 ret = i2c_smbus_read_word_data(client, reg);
if (ret < 0)
return ret;
/* LM75 使用大端字节序,需要交换 */
return (s16)swab16((u16)ret);
}
static int lm75_write_reg(struct i2c_client *client, u8 reg, s16 value)
{
return i2c_smbus_write_word_data(client, reg, swab16((u16)value));
}
/* ── 温度转换函数 ──────────────────────────────────────────── */
/* 将 LM75 原始值转换为毫摄氏度 */
static int lm75_reg_to_mc(s16 reg)
{
/* LM75 温度格式:高9位有效,bit7=0.5°C */
return (reg >> 7) * 500; /* 单位:毫摄氏度 */
}
/* 将毫摄氏度转换为 LM75 寄存器值 */
static s16 lm75_mc_to_reg(int mc)
{
return (s16)((mc / 500) << 7);
}
/* ── 更新缓存数据 ──────────────────────────────────────────── */
static struct lm75_data *lm75_update_device(struct device *dev)
{
struct lm75_data *data = dev_get_drvdata(dev);
struct i2c_client *client = data->client;
mutex_lock(&data->update_lock);
/* 每秒最多更新一次(缓存机制)*/
if (time_after(jiffies, data->last_updated + HZ) || !data->valid) {
s16 val;
val = lm75_read_reg(client, LM75_REG_TEMP);
if (val < 0) goto out;
data->temp[0] = val;
val = lm75_read_reg(client, LM75_REG_THYST);
if (val < 0) goto out;
data->temp[1] = val;
val = lm75_read_reg(client, LM75_REG_TOS);
if (val < 0) goto out;
data->temp[2] = val;
data->last_updated = jiffies;
data->valid = true;
}
out:
mutex_unlock(&data->update_lock);
return data;
}
/* ── sysfs 属性(通过 hwmon 框架)──────────────────────────── */
/* 读取当前温度 */
static ssize_t temp1_input_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct lm75_data *data = lm75_update_device(dev);
return sprintf(buf, "%d\n", lm75_reg_to_mc(data->temp[0]));
}
/* 读取过温阈值 */
static ssize_t temp1_max_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct lm75_data *data = lm75_update_device(dev);
return sprintf(buf, "%d\n", lm75_reg_to_mc(data->temp[2]));
}
/* 设置过温阈值 */
static ssize_t temp1_max_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct lm75_data *data = dev_get_drvdata(dev);
struct i2c_client *client = data->client;
long val;
int ret;
ret = kstrtol(buf, 10, &val);
if (ret)
return ret;
mutex_lock(&data->update_lock);
data->temp[2] = lm75_mc_to_reg(val);
lm75_write_reg(client, LM75_REG_TOS, data->temp[2]);
mutex_unlock(&data->update_lock);
return count;
}
/* 读取滞后温度 */
static ssize_t temp1_max_hyst_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct lm75_data *data = lm75_update_device(dev);
return sprintf(buf, "%d\n", lm75_reg_to_mc(data->temp[1]));
}
/* 设置滞后温度 */
static ssize_t temp1_max_hyst_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct lm75_data *data = dev_get_drvdata(dev);
struct i2c_client *client = data->client;
long val;
int ret;
ret = kstrtol(buf, 10, &val);
if (ret)
return ret;
mutex_lock(&data->update_lock);
data->temp[1] = lm75_mc_to_reg(val);
lm75_write_reg(client, LM75_REG_THYST, data->temp[1]);
mutex_unlock(&data->update_lock);
return count;
}
/* 定义 sysfs 属性 */
static DEVICE_ATTR_RO(temp1_input);
static DEVICE_ATTR_RW(temp1_max);
static DEVICE_ATTR_RW(temp1_max_hyst);
static struct attribute *lm75_attrs[] = {
&dev_attr_temp1_input.attr,
&dev_attr_temp1_max.attr,
&dev_attr_temp1_max_hyst.attr,
NULL
};
ATTRIBUTE_GROUPS(lm75);
/* ── probe 函数 ──────────────────────────────────────────────── */
static int lm75_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
struct lm75_data *data;
int ret;
/* 检查 I2C 适配器功能 */
if (!i2c_check_functionality(client->adapter,
I2C_FUNC_SMBUS_BYTE_DATA |
I2C_FUNC_SMBUS_WORD_DATA)) {
dev_err(&client->dev, "I2C 功能不支持\n");
return -ENODEV;
}
/* 分配驱动私有数据 */
data = devm_kzalloc(&client->dev, sizeof(*data), GFP_KERNEL);
if (!data)
return -ENOMEM;
data->client = client;
mutex_init(&data->update_lock);
/* 将私有数据保存到 client */
i2c_set_clientdata(client, data);
/* 读取初始温度,验证设备是否正常 */
ret = lm75_read_reg(client, LM75_REG_TEMP);
if (ret < 0) {
dev_err(&client->dev, "读取温度失败,设备可能不存在\n");
return ret;
}
/* 注册到 hwmon 子系统 */
data->hwmon_dev = devm_hwmon_device_register_with_groups(
&client->dev,
client->name,
data,
lm75_groups);
if (IS_ERR(data->hwmon_dev)) {
dev_err(&client->dev, "注册 hwmon 设备失败\n");
return PTR_ERR(data->hwmon_dev);
}
dev_info(&client->dev, "LM75 温度传感器初始化成功,地址 0x%02x\n",
client->addr);
/* 读取并打印初始温度 */
int temp_mc = lm75_reg_to_mc((s16)ret);
dev_info(&client->dev, "当前温度:%d.%d°C\n",
temp_mc / 1000, abs(temp_mc % 1000) / 100);
return 0;
}
static int lm75_remove(struct i2c_client *client)
{
dev_info(&client->dev, "LM75 驱动卸载\n");
return 0;
}
/* ── 设备 ID 表 ──────────────────────────────────────────────── */
static const struct i2c_device_id lm75_id[] = {
{ "lm75", 0 },
{ "lm75a", 0 },
{ "lm75b", 0 },
{}
};
MODULE_DEVICE_TABLE(i2c, lm75_id);
/* 设备树匹配表 */
static const struct of_device_id lm75_of_match[] = {
{ .compatible = "national,lm75" },
{ .compatible = "national,lm75a" },
{ .compatible = "ti,tmp75" },
{}
};
MODULE_DEVICE_TABLE(of, lm75_of_match);
/* ── I2C 驱动结构体 ──────────────────────────────────────────── */
static struct i2c_driver lm75_driver = {
.driver = {
.name = "lm75",
.of_match_table = lm75_of_match,
},
.probe = lm75_probe,
.remove = lm75_remove,
.id_table = lm75_id,
};
/* 简化的模块注册宏 */
module_i2c_driver(lm75_driver);
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("参考宋宝华《Linux设备驱动开发详解》");
MODULE_DESCRIPTION("LM75 温度传感器 I2C 驱动");
15.4.3 设备树中的 I2C 设备描述
dts
/* 设备树中描述 I2C 总线和设备 */
&i2c1 {
clock-frequency = <400000>; /* I2C 时钟频率:400kHz(快速模式)*/
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_i2c1>;
status = "okay";
/* LM75 温度传感器,地址 0x48(A2A1A0=000)*/
lm75: temperature-sensor@48 {
compatible = "national,lm75";
reg = <0x48>; /* I2C 从设备地址 */
/* 可选:中断引脚(OS/INT 引脚)*/
interrupt-parent = <&gpio1>;
interrupts = <5 IRQ_TYPE_LEVEL_LOW>;
};
/* AT24C02 EEPROM,地址 0x50(A2A1A0=000)*/
eeprom@50 {
compatible = "atmel,24c02";
reg = <0x50>;
pagesize = <8>; /* 页大小:8字节 */
};
/* MPU6050 六轴传感器,地址 0x68(AD0=0)*/
mpu6050@68 {
compatible = "invensense,mpu6050";
reg = <0x68>;
interrupt-parent = <&gpio2>;
interrupts = <3 IRQ_TYPE_EDGE_FALLING>;
};
};
15.4.4 I2C 设备驱动的数据传输案例
c
/*
* 案例:MPU6050 六轴传感器驱动(读取加速度和陀螺仪数据)
* MPU6050 I2C 地址:0x68(AD0=0)或 0x69(AD0=1)
*/
/* MPU6050 寄存器定义 */
#define MPU6050_REG_SMPLRT_DIV 0x19 /* 采样率分频 */
#define MPU6050_REG_CONFIG 0x1A /* 配置 */
#define MPU6050_REG_GYRO_CONFIG 0x1B /* 陀螺仪配置 */
#define MPU6050_REG_ACCEL_CONFIG 0x1C /* 加速度计配置 */
#define MPU6050_REG_ACCEL_XOUT_H 0x3B /* 加速度 X 轴高字节 */
#define MPU6050_REG_GYRO_XOUT_H 0x43 /* 陀螺仪 X 轴高字节 */
#define MPU6050_REG_TEMP_OUT_H 0x41 /* 温度高字节 */
#define MPU6050_REG_PWR_MGMT_1 0x6B /* 电源管理1 */
#define MPU6050_REG_WHO_AM_I 0x75 /* 设备 ID(应为 0x68)*/
/* 初始化 MPU6050 */
static int mpu6050_init(struct i2c_client *client)
{
int ret;
/* 验证设备 ID */
ret = i2c_smbus_read_byte_data(client, MPU6050_REG_WHO_AM_I);
if (ret < 0 || ret != 0x68) {
dev_err(&client->dev, "无效的设备 ID:0x%02x\n", ret);
return -ENODEV;
}
/* 唤醒设备(清除睡眠位)*/
ret = i2c_smbus_write_byte_data(client, MPU6050_REG_PWR_MGMT_1, 0x00);
if (ret) return ret;
/* 设置采样率:1kHz / (1 + 7) = 125Hz */
ret = i2c_smbus_write_byte_data(client, MPU6050_REG_SMPLRT_DIV, 0x07);
if (ret) return ret;
/* 设置陀螺仪量程:±250°/s */
ret = i2c_smbus_write_byte_data(client, MPU6050_REG_GYRO_CONFIG, 0x00);
if (ret) return ret;
/* 设置加速度计量程:±2g */
ret = i2c_smbus_write_byte_data(client, MPU6050_REG_ACCEL_CONFIG, 0x00);
if (ret) return ret;
dev_info(&client->dev, "MPU6050 初始化成功\n");
return 0;
}
/* 读取加速度和陀螺仪数据(使用块读,一次读取14字节)*/
static int mpu6050_read_data(struct i2c_client *client,
s16 *accel, s16 *gyro, s16 *temp)
{
u8 buf[14];
int ret;
/*
* 使用 i2c_smbus_read_i2c_block_data 一次读取14字节:
* ACCEL_XOUT_H, ACCEL_XOUT_L, ACCEL_YOUT_H, ACCEL_YOUT_L,
* ACCEL_ZOUT_H, ACCEL_ZOUT_L, TEMP_OUT_H, TEMP_OUT_L,
* GYRO_XOUT_H, GYRO_XOUT_L, GYRO_YOUT_H, GYRO_YOUT_L,
* GYRO_ZOUT_H, GYRO_ZOUT_L
*/
ret = i2c_smbus_read_i2c_block_data(client,
MPU6050_REG_ACCEL_XOUT_H,
14, buf);
if (ret != 14) {
dev_err(&client->dev, "读取传感器数据失败:%d\n", ret);
return ret < 0 ? ret : -EIO;
}
/* 解析加速度数据(大端字节序)*/
accel[0] = (s16)((buf[0] << 8) | buf[1]); /* X 轴 */
accel[1] = (s16)((buf[2] << 8) | buf[3]); /* Y 轴 */
accel[2] = (s16)((buf[4] << 8) | buf[5]); /* Z 轴 */
/* 解析温度数据 */
*temp = (s16)((buf[6] << 8) | buf[7]);
/* 解析陀螺仪数据 */
gyro[0] = (s16)((buf[8] << 8) | buf[9]); /* X 轴 */
gyro[1] = (s16)((buf[10] << 8) | buf[11]); /* Y 轴 */
gyro[2] = (s16)((buf[12] << 8) | buf[13]); /* Z 轴 */
return 0;
}
15.5 I2C 设备驱动与用户空间交互
15.5.1 通过 /dev/i2c-N 直接访问
Linux 提供了 /dev/i2c-N 设备文件,允许用户空间程序直接访问 I2C 总线,无需编写内核驱动:
c
/*
* 用户空间通过 /dev/i2c-N 访问 I2C 设备
* 需要包含 <linux/i2c-dev.h>
*/
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <linux/i2c.h>
#include <linux/i2c-dev.h>
int main(void)
{
int fd;
u8 buf[2];
int ret;
/* 打开 I2C 总线 */
fd = open("/dev/i2c-1", O_RDWR);
if (fd < 0) {
perror("open /dev/i2c-1 failed");
return -1;
}
/* 设置从设备地址(LM75,地址 0x48)*/
ret = ioctl(fd, I2C_SLAVE, 0x48);
if (ret < 0) {
perror("ioctl I2C_SLAVE failed");
close(fd);
return -1;
}
/* 方法一:使用 read/write 系统调用 */
/* 写:发送寄存器地址(选择温度寄存器 0x00)*/
buf[0] = 0x00;
ret = write(fd, buf, 1);
if (ret != 1) {
perror("write failed");
close(fd);
return -1;
}
/* 读:读取2字节温度数据 */
ret = read(fd, buf, 2);
if (ret != 2) {
perror("read failed");
close(fd);
return -1;
}
/* 解析温度(大端字节序)*/
s16 raw = (s16)((buf[0] << 8) | buf[1]);
raw >>= 5;
int temp_mc = (raw / 2) * 1000 + (raw & 1) * 500;
printf("LM75 温度:%d.%d°C\n", temp_mc / 1000, abs(temp_mc % 1000) / 100);
/* 方法二:使用 I2C_RDWR ioctl(支持复合消息)*/
struct i2c_rdwr_ioctl_data rdwr;
struct i2c_msg msgs[2];
u8 reg_addr = 0x00;
u8 data[2];
msgs[0].addr = 0x48;
msgs[0].flags = 0; /* 写 */
msgs[0].len = 1;
msgs[0].buf = ®_addr;
msgs[1].addr = 0x48;
msgs[1].flags = I2C_M_RD; /* 读 */
msgs[1].len = 2;
msgs[1].buf = data;
rdwr.msgs = msgs;
rdwr.nmsgs = 2;
ret = ioctl(fd, I2C_RDWR, &rdwr);
if (ret < 0) {
perror("ioctl I2C_RDWR failed");
close(fd);
return -1;
}
raw = (s16)((data[0] << 8) | data[1]);
raw >>= 5;
temp_mc = (raw / 2) * 1000 + (raw & 1) * 500;
printf("LM75 温度(I2C_RDWR):%d.%d°C\n",
temp_mc / 1000, abs(temp_mc % 1000) / 100);
/* 方法三:使用 SMBus ioctl */
union i2c_smbus_data smbus_data;
struct i2c_smbus_ioctl_data smbus_args;
smbus_args.read_write = I2C_SMBUS_READ;
smbus_args.command = 0x00; /* 温度寄存器 */
smbus_args.size = I2C_SMBUS_WORD_DATA;
smbus_args.data = &smbus_data;
ret = ioctl(fd, I2C_SMBUS, &smbus_args);
if (ret < 0) {
perror("ioctl I2C_SMBUS failed");
close(fd);
return -1;
}
printf("LM75 温度(SMBus):原始值 0x%04x\n", smbus_data.word);
close(fd);
return 0;
}
15.5.2 通过 sysfs 访问 I2C 设备
当 I2C 设备驱动加载后,可以通过 sysfs 访问设备属性:
bash
# 查看 I2C 总线上的设备
ls /sys/bus/i2c/devices/
# i2c-0 i2c-1 1-0048 1-0050 1-0068
# 格式:总线号-设备地址(十六进制)
# 查看 LM75 温度传感器(总线1,地址0x48)
ls /sys/bus/i2c/devices/1-0048/
# driver hwmon modalias name power subsystem uevent
# 通过 hwmon 接口读取温度
ls /sys/bus/i2c/devices/1-0048/hwmon/hwmon0/
# device name power subsystem temp1_input temp1_max temp1_max_hyst uevent
# 读取当前温度(单位:毫摄氏度)
cat /sys/bus/i2c/devices/1-0048/hwmon/hwmon0/temp1_input
# 25500 ← 25.5°C
# 读取过温阈值
cat /sys/bus/i2c/devices/1-0048/hwmon/hwmon0/temp1_max
# 80000 ← 80°C
# 设置过温阈值
echo 75000 > /sys/bus/i2c/devices/1-0048/hwmon/hwmon0/temp1_max
# 查看 AT24C02 EEPROM
ls /sys/bus/i2c/devices/1-0050/
# driver eeprom modalias name power subsystem uevent
# 读取 EEPROM 内容
hexdump -C /sys/bus/i2c/devices/1-0050/eeprom | head -5
# 00000000 ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
15.5.3 i2c-tools 工具集
Linux 提供了 i2c-tools 工具集,方便调试 I2C 设备:
bash
# 安装 i2c-tools
sudo apt-get install i2c-tools
# ── i2cdetect:扫描 I2C 总线上的设备 ──────────────────────
sudo i2cdetect -y 1
# 0 1 2 3 4 5 6 7 8 9 a b c d e f
# 00: -- -- -- -- -- -- -- -- -- -- -- -- --
# 10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
# 20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
# 30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
# 40: -- -- -- -- -- -- -- -- 48 -- -- -- -- -- -- --
# 50: 50 -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
# 60: -- -- -- -- -- -- -- -- 68 -- -- -- -- -- -- --
# 70: -- -- -- -- -- -- -- --
# 0x48 = LM75, 0x50 = AT24C02, 0x68 = MPU6050
# ── i2cget:读取 I2C 设备寄存器 ────────────────────────────
# 读取 LM75(总线1,地址0x48)温度寄存器(0x00),字模式
sudo i2cget -y 1 0x48 0x00 w
# 0x1900 ← 大端字节序,需要交换:0x0019 = 25°C
# 读取 MPU6050 设备 ID 寄存器(0x75)
sudo i2cget -y 1 0x68 0x75 b
# 0x68 ← 设备 ID,确认是 MPU6050
# ── i2cset:写入 I2C 设备寄存器 ────────────────────────────
# 设置 LM75 过温阈值寄存器(0x03)为 80°C
# 80°C = 0x5000(大端)
sudo i2cset -y 1 0x48 0x03 0x5000 w
# 唤醒 MPU6050(写电源管理寄存器 0x6B = 0x00)
sudo i2cset -y 1 0x68 0x6B 0x00 b
# ── i2cdump:转储 I2C 设备所有寄存器 ───────────────────────
sudo i2cdump -y 1 0x68 b
# 0 1 2 3 4 5 6 7 8 9 a b c d e f
# 00: XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX
# ...
# 70: XX XX XX XX XX 68 XX XX XX XX XX XX XX XX XX XX
# 0x75 = 0x68(WHO_AM_I 寄存器)
# ── i2ctransfer:执行复合 I2C 传输 ─────────────────────────
# 写1字节(寄存器地址0x00),然后读2字节(温度数据)
sudo i2ctransfer -y 1 w1@0x48 0x00 r2@0x48
# 0x19 0x00 ← 25°C(大端)
15.5.4 通过字符设备接口访问 I2C 设备
驱动可以创建字符设备文件,提供更友好的用户空间接口:
c
/*
* 在 I2C 设备驱动中创建字符设备接口
* 允许用户空间通过 /dev/lm75 访问温度传感器
*/
static int lm75_cdev_open(struct inode *inode, struct file *filp)
{
struct lm75_data *data = container_of(inode->i_cdev,
struct lm75_data, cdev);
filp->private_data = data;
return 0;
}
static ssize_t lm75_cdev_read(struct file *filp, char __user *buf,
size_t count, loff_t *ppos)
{
struct lm75_data *data = filp->private_data;
char temp_str[32];
int temp_mc;
ssize_t len;
/* 读取温度 */
s16 raw = lm75_read_reg(data->client, LM75_REG_TEMP);
temp_mc = lm75_reg_to_mc(raw);
len = snprintf(temp_str, sizeof(temp_str), "%d.%d\n",
temp_mc / 1000, abs(temp_mc % 1000) / 100);
if (copy_to_user(buf, temp_str, len))
return -EFAULT;
return len;
}
static const struct file_operations lm75_cdev_fops = {
.owner = THIS_MODULE,
.open = lm75_cdev_open,
.read = lm75_cdev_read,
};
/* 在 probe 中创建字符设备 */
static int lm75_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
struct lm75_data *data;
/* ... 其他初始化 ... */
/* 创建字符设备 */
alloc_chrdev_region(&data->devno, 0, 1, "lm75");
cdev_init(&data->cdev, &lm75_cdev_fops);
cdev_add(&data->cdev, data->devno, 1);
data->class = class_create(THIS_MODULE, "lm75");
device_create(data->class, NULL, data->devno, NULL, "lm75");
return 0;
}
/* 用户空间访问 */
/* cat /dev/lm75 */
/* 25.5 */
本章小结
| 章节 | 核心知识点 | 关键 API |
|---|---|---|
| 15.1 I2C体系结构 | I2C总线物理连接;Linux I2C三层架构图;i2c_adapter/i2c_client/i2c_driver三个核心概念及关系 | 概念理解 |
| 15.2 I2C核心 | i2c_adapter/i2c_algorithm/i2c_msg结构体;适配器/驱动管理API;i2c_transfer底层接口;SMBus高层接口(read_byte_data/write_byte_data/read_word_data等) | i2c_transfer()、i2c_smbus_read_byte_data()、i2c_smbus_read_i2c_block_data() |
| 15.3 I2C适配器驱动 | master_xfer实现(START/数据传输/STOP);中断+completion同步;i2c_add_adapter注册;完整适配器驱动代码 | i2c_add_adapter()、i2c_set_adapdata()、i2c_get_adapdata() |
| 15.4 I2C设备驱动 | i2c_driver/i2c_client结构体;LM75完整驱动(probe/sysfs属性/hwmon注册);MPU6050块读案例;设备树配置 | i2c_set_clientdata()、i2c_get_clientdata()、module_i2c_driver() |
| 15.5 用户空间交互 | /dev/i2c-N直接访问(I2C_SLAVE/I2C_RDWR/I2C_SMBUS ioctl);sysfs hwmon接口;i2c-tools工具集(i2cdetect/i2cget/i2cset/i2cdump/i2ctransfer);字符设备接口 | ioctl(I2C_SLAVE)、ioctl(I2C_RDWR)、i2cdetect、i2cget |
I2C 驱动开发要点
1. 选择合适的传输接口
简单读写 → i2c_smbus_read/write_byte_data(最简单)
16位数据 → i2c_smbus_read/write_word_data
批量数据 → i2c_smbus_read_i2c_block_data(最多32字节)
复杂传输 → i2c_transfer(最灵活,支持任意消息组合)
2. 检查适配器功能
probe 中调用 i2c_check_functionality 验证所需功能
3. 使用 devm_ 系列函数
devm_kzalloc 分配私有数据,自动释放
4. 设备树配置
reg = <地址>(必须)
clock-frequency(I2C 总线频率)
interrupts(中断引脚,可选)
5. 调试工具
i2cdetect 扫描设备
i2cget/i2cset 读写寄存器
i2cdump 转储所有寄存器
参考文献:宋宝华《Linux设备驱动开发详解:基于最新的Linux 4.0内核》,机械工业出版社,2015年