一、顶级架构一句话总结
i2c_adapter(控制器) ← I2C核心层 → i2c_client(设备) ← i2c_driver(驱动)
I2C子系统采用分层架构,将控制器驱动与设备驱动分离,实现高度复用。
二、I2C子系统架构
架构层次
┌─────────────────────────────────────────────────────────┐
│ I2C子系统架构 │
├─────────────────────────────────────────────────────────┤
│ │
│ 用户空间 /dev/i2c-X 或 sysfs │
│ ↓ │
│ I2C设备驱动 i2c_driver (传感器、EEPROM等) │
│ ↓ │
│ I2C核心层 i2c-core.c (统一抽象层) │
│ ↓ │
│ I2C控制器 i2c_adapter (SoC厂商驱动) │
│ ↓ │
│ 硬件层 I2C寄存器操作 │
│ │
└─────────────────────────────────────────────────────────┘
核心数据结构
c
// I2C适配器(控制器)
struct i2c_adapter {
struct module *owner;
unsigned int class;
const struct i2c_algorithm *algo; // 传输算法
void *algo_data;
struct device dev;
int nr; // 总线编号
char name[48];
};
// I2C算法
struct i2c_algorithm {
int (*master_xfer)(struct i2c_adapter *adap, struct i2c_msg *msgs, int num);
int (*smbus_xfer)(struct i2c_adapter *adap, u16 addr, unsigned short flags,
char read_write, u8 command, int size, union i2c_smbus_data *data);
u32 (*functionality)(struct i2c_adapter *adap);
};
// I2C客户端(设备)
struct i2c_client {
unsigned short flags; // 标志
unsigned short addr; // 设备地址
char name[I2C_NAME_SIZE]; // 设备名称
struct i2c_adapter *adapter; // 所属适配器
struct device dev; // 内嵌device
int irq; // 中断号
};
// I2C驱动
struct i2c_driver {
unsigned int class;
int (*probe)(struct i2c_client *client, const struct i2c_device_id *id);
int (*remove)(struct i2c_client *client);
struct device_driver driver;
const struct i2c_device_id *id_table;
const struct of_device_id *of_match_table;
};
三、I2C消息传输
i2c_msg结构
c
struct i2c_msg {
__u16 addr; // 设备地址
__u16 flags; // 消息标志
__u16 len; // 数据长度
__u8 *buf; // 数据缓冲区
};
// 常用标志
#define I2C_M_RD 0x0001 // 读操作
#define I2C_M_TEN 0x0010 // 10位地址
#define I2C_M_STOP 0x8000 // 发送STOP
#define I2C_M_NOSTART 0x4000 // 不发送START
传输函数
c
// 原始传输函数
int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num);
// SMBus传输
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);
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_block_data(const struct i2c_client *client, u8 command, u8 *values);
s32 i2c_smbus_write_block_data(const struct i2c_client *client, u8 command, u8 length, const u8 *values);
四、设备树配置
I2C设备树节点
dts
&i2c1 {
status = "okay";
clock-frequency = <400000>; // 400KHz
// 温度传感器
temp_sensor: lm75@48 {
compatible = "national,lm75";
reg = <0x48>; // I2C地址
};
// EEPROM
eeprom@50 {
compatible = "atmel,24c02";
reg = <0x50>;
pagesize = <8>;
};
// 触摸屏
touchscreen@38 {
compatible = "focaltech,ft5x06";
reg = <0x38>;
interrupt-parent = <&gpio>;
interrupts = <17 IRQ_TYPE_EDGE_FALLING>;
reset-gpios = <&gpio 16 GPIO_ACTIVE_LOW>;
};
};
五、I2C设备驱动示例
EEPROM驱动
c
#include <linux/module.h>
#include <linux/i2c.h>
#include <linux/slab.h>
#define EEPROM_SIZE 256
struct eeprom_data {
struct i2c_client *client;
struct mutex lock;
u8 *buffer;
};
// 读取EEPROM
static int eeprom_read(struct eeprom_data *data, u8 offset, u8 *buf, int len)
{
struct i2c_client *client = data->client;
struct i2c_msg msgs[2];
int ret;
mutex_lock(&data->lock);
// 第一条消息:发送要读取的地址
msgs[0].addr = client->addr;
msgs[0].flags = 0;
msgs[0].len = 1;
msgs[0].buf = &offset;
// 第二条消息:读取数据
msgs[1].addr = client->addr;
msgs[1].flags = I2C_M_RD;
msgs[1].len = len;
msgs[1].buf = buf;
ret = i2c_transfer(client->adapter, msgs, 2);
mutex_unlock(&data->lock);
return ret == 2 ? len : ret;
}
// 写入EEPROM
static int eeprom_write(struct eeprom_data *data, u8 offset, const u8 *buf, int len)
{
struct i2c_client *client = data->client;
u8 *write_buf;
int ret;
write_buf = kmalloc(len + 1, GFP_KERNEL);
if (!write_buf)
return -ENOMEM;
mutex_lock(&data->lock);
write_buf[0] = offset;
memcpy(&write_buf[1], buf, len);
ret = i2c_master_send(client, write_buf, len + 1);
mutex_unlock(&data->lock);
kfree(write_buf);
return ret > 0 ? len : ret;
}
static int eeprom_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
struct eeprom_data *data;
data = devm_kzalloc(&client->dev, sizeof(*data), GFP_KERNEL);
if (!data)
return -ENOMEM;
data->client = client;
mutex_init(&data->lock);
i2c_set_clientdata(client, data);
dev_info(&client->dev, "EEPROM probed at 0x%02x\n", client->addr);
return 0;
}
static int eeprom_remove(struct i2c_client *client)
{
dev_info(&client->dev, "EEPROM removed\n");
return 0;
}
static const struct i2c_device_id eeprom_id[] = {
{ "eeprom_24c02", 0 },
{ }
};
MODULE_DEVICE_TABLE(i2c, eeprom_id);
static const struct of_device_id eeprom_of_match[] = {
{ .compatible = "atmel,24c02", },
{ }
};
MODULE_DEVICE_TABLE(of, eeprom_of_match);
static struct i2c_driver eeprom_driver = {
.probe = eeprom_probe,
.remove = eeprom_remove,
.id_table = eeprom_id,
.driver = {
.name = "eeprom_24c02",
.of_match_table = eeprom_of_match,
},
};
module_i2c_driver(eeprom_driver);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Driver Developer");
六、SMBus操作示例
温度传感器驱动
c
#include <linux/module.h>
#include <linux/i2c.h>
#include <linux/hwmon.h>
#define LM75_TEMP_REG 0x00
#define LM75_CONF_REG 0x01
#define LM75_THYST_REG 0x02
#define LM75_TOS_REG 0x03
struct lm75_data {
struct i2c_client *client;
struct mutex lock;
};
// 读取温度(SMBus方式)
static int lm75_read_temp(struct lm75_data *data)
{
struct i2c_client *client = data->client;
s32 raw_temp;
int temp;
mutex_lock(&data->lock);
// 使用SMBus读取16位数据
raw_temp = i2c_smbus_read_word_swapped(client, LM75_TEMP_REG);
mutex_unlock(&data->lock);
if (raw_temp < 0)
return raw_temp;
// 转换为摄氏度(0.5度精度)
temp = (raw_temp >> 5) * 125;
return temp;
}
// 设置过温阈值
static int lm75_set_threshold(struct lm75_data *data, int temp)
{
struct i2c_client *client = data->client;
u16 value;
int ret;
// 转换为寄存器格式
value = (temp / 500) << 7;
mutex_lock(&data->lock);
ret = i2c_smbus_write_word_swapped(client, LM75_TOS_REG, value);
mutex_unlock(&data->lock);
return ret;
}
static int lm75_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
struct lm75_data *data;
if (!i2c_check_functionality(client->adapter,
I2C_FUNC_SMBUS_BYTE_DATA |
I2C_FUNC_SMBUS_WORD_DATA))
return -EIO;
data = devm_kzalloc(&client->dev, sizeof(*data), GFP_KERNEL);
if (!data)
return -ENOMEM;
data->client = client;
mutex_init(&data->lock);
i2c_set_clientdata(client, data);
dev_info(&client->dev, "LM75 temperature sensor probed\n");
return 0;
}
static const struct i2c_device_id lm75_id[] = {
{ "lm75", 0 },
{ }
};
MODULE_DEVICE_TABLE(i2c, lm75_id);
static const struct of_device_id lm75_of_match[] = {
{ .compatible = "national,lm75", },
{ }
};
MODULE_DEVICE_TABLE(of, lm75_of_match);
static struct i2c_driver lm75_driver = {
.probe = lm75_probe,
.id_table = lm75_id,
.driver = {
.name = "lm75",
.of_match_table = lm75_of_match,
},
};
module_i2c_driver(lm75_driver);
MODULE_LICENSE("GPL");
七、I2C控制器驱动
简化的控制器驱动框架
c
static int my_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)
{
struct my_i2c_dev *dev = i2c_get_adapdata(adap);
int i, ret;
for (i = 0; i < num; i++) {
// 发送START
my_i2c_start(dev);
// 发送地址
ret = my_i2c_send_addr(dev, msgs[i].addr, msgs[i].flags & I2C_M_RD);
if (ret < 0)
break;
// 传输数据
if (msgs[i].flags & I2C_M_RD)
ret = my_i2c_read(dev, msgs[i].buf, msgs[i].len);
else
ret = my_i2c_write(dev, msgs[i].buf, msgs[i].len);
if (ret < 0)
break;
}
// 发送STOP
my_i2c_stop(dev);
return i;
}
static u32 my_i2c_func(struct i2c_adapter *adap)
{
return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL;
}
static const struct i2c_algorithm my_i2c_algo = {
.master_xfer = my_i2c_xfer,
.functionality = my_i2c_func,
};
static int my_i2c_probe(struct platform_device *pdev)
{
struct my_i2c_dev *dev;
struct i2c_adapter *adap;
int ret;
dev = devm_kzalloc(&pdev->dev, sizeof(*dev), GFP_KERNEL);
if (!dev)
return -ENOMEM;
// 初始化硬件...
adap = &dev->adapter;
adap->owner = THIS_MODULE;
adap->algo = &my_i2c_algo;
adap->dev.parent = &pdev->dev;
snprintf(adap->name, sizeof(adap->name), "my_i2c");
i2c_set_adapdata(adap, dev);
ret = i2c_add_adapter(adap);
if (ret)
return ret;
platform_set_drvdata(pdev, dev);
return 0;
}
static int my_i2c_remove(struct platform_device *pdev)
{
struct my_i2c_dev *dev = platform_get_drvdata(pdev);
i2c_del_adapter(&dev->adapter);
return 0;
}
八、调试技巧
查看I2C总线
bash
# 查看I2C总线
ls /dev/i2c-*
# 查看I2C设备
i2cdetect -l
i2cdetect -y 1
# 读取寄存器
i2cget -y 1 0x48 0x00
# 写入寄存器
i2cset -y 1 0x48 0x00 0x55
# dump寄存器
i2cdump -y 1 0x48
内核调试
bash
# 查看I2C设备
ls /sys/bus/i2c/devices/
# 查看设备信息
cat /sys/bus/i2c/devices/1-0048/name
# 查看内核日志
dmesg | grep -i i2c
九、终极总结
I2C子系统 = 分层架构的典范
- 核心概念:adapter(控制器)、client(设备)、driver(驱动)
- 传输方式:i2c_transfer(原始)、SMBus(简化)
- 设备树:通过reg属性指定I2C地址
- 调试工具:i2cdetect、i2cget、i2cset
掌握I2C子系统,就掌握了总线类驱动的开发模式!