作者:嵌入式Jerry
视频教程请关注 B 站:“嵌入式Jerry”
一、写在前面
在上一阶段我们已经深入理解了字符设备驱动与设备模型之间的结合方式、sysfs 的创建方式以及平台驱动模型的实际运用。今天我们迈入总线驱动模型的世界,聚焦于 I2C 总线驱动模型,并选取一个典型、真实且广泛使用的驱动设备 —— at24 EEPROM,进行完整分析。
本篇内容将从 I2C 总线模型的架构出发,讲清楚:
- 驱动如何注册到 I2C 总线上?
- I2C client 是如何匹配的?
- at24 是如何成为标准驱动代表的?
- 如何配置设备树节点?
- 如何验证实际读写?
并配套提供真实代码与调试示例。
二、I2C 总线驱动模型架构简介
在 Linux 内核中,I2C 总线驱动模型是基于总线-设备-驱动的三层结构之上实现的:
I2C 总线(i2c_adapter) ←→ I2C 设备(i2c_client) ←→ I2C 驱动(i2c_driver)
i2c_adapter
:抽象了一个物理 I2C 控制器(主机控制器)i2c_client
:抽象了挂在某条 adapter 上的 I2C 外设i2c_driver
:抽象了对某种类型 I2C 设备的驱动代码
这和设备模型中的 bus_type
、device
、device_driver
是一致的,i2c-core
就是 bus_type
的实现,负责完成 match 和 probe。
三、典型案例介绍:at24 EEPROM
我们选择 i2c 子系统中的一个经典外设:at24 系列 EEPROM。它们遵循 I2C 协议,容量常见为 16Kbit/32Kbit/64Kbit,支持页写和顺序读。
在 Linux 内核中,驱动文件为:
drivers/misc/eeprom/at24.c
其本质是一个标准 i2c_driver
驱动。
四、设备树配置
在 NXP i.MX8MP 平台中,at24 EEPROM 可以通过设备树添加,典型配置如下:
&i2c3 {
status = "okay";
clock-frequency = <400000>;
eeprom@50 {
compatible = "atmel,24c16";
reg = <0x50>;
pagesize = <16>;
};
};
解释:
compatible
:用于匹配驱动reg
:I2C 地址(7 位)pagesize
:页写大小(单位:字节)
五、驱动入口分析
at24.c
中的核心驱动结构如下:
static const struct of_device_id at24_of_match[] = {
{ .compatible = "atmel,24c16", .data = (void *)AT24_DEVICE_MAGIC },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, at24_of_match);
static struct i2c_driver at24_driver = {
.driver = {
.name = "at24",
.of_match_table = of_match_ptr(at24_of_match),
},
.probe_new = at24_probe,
.remove = at24_remove,
.id_table = at24_ids,
};
module_i2c_driver(at24_driver);
🔍 核心点解析:
- 使用
of_match_table
进行设备树匹配; - 使用
module_i2c_driver
宏注册; - probe 函数负责初始化工作,注册字符设备。
六、字符设备注册逻辑
at24 实现为字符设备,允许用户从 /dev
直接读写 EEPROM 数据。
ret = devm_device_add_groups(&client->dev, at24_groups);
cdev_init(&at24->cdev, &at24_fops);
ret = cdev_add(&at24->cdev, at24->devt, 1);
device_create(...);
最终会创建出:
/dev/eeprom
/sys/class/eeprom/eeprom0
文件操作如下:
static const struct file_operations at24_fops = {
.owner = THIS_MODULE,
.llseek = no_llseek,
.read = at24_read,
.write = at24_write,
.open = at24_open,
};
七、匹配过程剖析
整个驱动匹配过程:
at24
驱动通过of_match_table
注册支持"atmel,24c16"
i2c-core
扫描设备树时发现此节点- 创建
i2c_client
,并匹配i2c_driver
- 调用
at24_probe()
,完成驱动绑定
八、调试验证操作
加载驱动后会创建字符设备:
ls /dev/eeprom
我们可以直接写入:
echo "hello" > /dev/eeprom
再读取:
hexdump -C /dev/eeprom
若权限不足,可添加 udev 规则或通过 root 权限访问。
九、at24 驱动完整结构总览
+-----------------------------+
| I2C Adapter |
| (控制器,如i.MX8MP) |
+-------------+-------------+
|
v
+-------------+-------------+
| I2C Client | ←─────────────── 由设备树注册
| i2c_client @ 0x50 |
+-------------+-------------+
|
v
+-------------+-------------+
| I2C Driver | ←─────────────── drivers/misc/eeprom/at24.c
| i2c_driver: at24 |
+-------------+-------------+
|
v
+-------------+-------------+
| Character Device | ←─────────────── /dev/eeprom
| file_operations: fops |
+---------------------------+
🔟 总结与思考
通过 at24 EEPROM 的驱动分析,我们掌握了 I2C 总线驱动模型的完整机制:
关键组成 | 说明 |
---|---|
i2c_adapter |
控制器抽象,如 i2c3 |
i2c_client |
挂载在 adapter 上的设备 |
i2c_driver |
与 client 匹配的驱动 |
device tree |
提供匹配入口 |
字符设备 |
提供用户访问接口 |
相比 platform 模型,I2C 总线驱动具有自动枚举能力,更适合标准器件(如 EEPROM、音频 codec、传感器等)。
✅ 今日任务巩固
- I2C 总线模型中,
client
与driver
如何匹配? - at24 驱动中,字符设备注册在哪个函数中?
- 如何通过设备树指定 I2C 地址与页大小?
- EEPROM 驱动是否需要实现
read/write
? - 与 platform_driver 模型的区别有哪些?
📢 后续预告
在下一篇(Day 30 下篇)中,我们将围绕 lm48100q 音频 codec 展开,深入讲解 音频子系统 + I2C 驱动模型 的高级应用与集成技巧,敬请期待!
本文由 嵌入式Jerry 原创发布,转载请注明出处
视频教程请关注 B 站:“嵌入式Jerry”