一份能直接跑的 Linux I2C 设备驱动讲解:什么时候真的需要内核驱动、驱动骨架、设备树节点、regmap、延后探测,以及设备没出现时的排查清单。
很多 I2C 设备其实不需要内核驱动。Linux 通过 i2c-dev 接口把每一个 I2C 适配器暴露给用户空间,用 i2c-tools 工具或一个小程序就能读写寄存器。对于原型、一次性使用的传感器,或者只有你自己的应用会访问的设备,这是最快的路,而且你的代码出错时不会连累内核。
出现下面任一情况时,才写内核驱动:
对于传感器,在自己发明接口之前,先看一下工业 I/O(IIO)子系统。它已经为用户空间提供了统一的读取、缩放和缓冲采样的方式。
下面是一个完整的最小驱动,对应一个假想的、寄存器为 8 位的传感器。它在 probe 时读取芯片标识,别的什么都不做,这正是第一步点亮想要的:在写任何功能代码之前,先证明总线、地址、供电和绑定都是通的。
#include <linux/module.h>
#include <linux/i2c.h>
#include <linux/regmap.h>
#define REG_ID 0x00
static const struct regmap_config demo_regmap = {
.reg_bits = 8,
.val_bits = 8,
};
static int demo_probe(struct i2c_client *client)
{
struct regmap *map;
unsigned int id;
int ret;
map = devm_regmap_init_i2c(client, &demo_regmap);
if (IS_ERR(map))
return PTR_ERR(map);
ret = regmap_read(map, REG_ID, &id);
if (ret)
return dev_err_probe(&client->dev, ret, "cannot read chip id\n");
dev_info(&client->dev, "chip id 0x%02x\n", id);
i2c_set_clientdata(client, map);
return 0;
}
static const struct of_device_id demo_of_match[] = {
{ .compatible = "acme,demo-sensor" },
{ }
};
MODULE_DEVICE_TABLE(of, demo_of_match);
static struct i2c_driver demo_driver = {
.driver = {
.name = "demo-sensor",
.of_match_table = demo_of_match,
},
.probe = demo_probe,
};
module_i2c_driver(demo_driver);
MODULE_LICENSE("GPL");
有三个细节要注意。上面这种只有 client 一个参数的 probe 写法,是 6.3 及以后内核使用的,更老的内核签名不同,要看你编译所用的版本。devm_ 系列函数会在设备消失时自动释放资源,省掉大部分清理代码和大部分泄漏。regmap 负责寄存器访问、缓存和总线差异,同一份寄存器描述,将来这颗芯片提供 SPI 接口时也能复用。
驱动之所以能绑定到硬件,是因为设备树声明了这个硬件存在。节点放在它所连接的 I2C 控制器下面:
&i2c1 {
status = "okay";
pinctrl-names = "default";
pinctrl-0 = <&i2c1_pins>;
sensor@44 {
compatible = "acme,demo-sensor";
reg = <0x44>;
};
};
compatible 字符串必须和驱动里的表逐字符完全一致。reg 是 7 位 I2C 地址,不含读写位,最常见的错误值,就是把数据手册里的 8 位地址直接抄了进来。供电、复位和中断线也属于这个节点,作为驱动读取的属性,这样硬件有变动时,改的是设备树,而不是代码。
按顺序走完下面的清单,每一步都能排除一整类问题。
动态调试(dyndbg)可以在运行时打开某个驱动的调试信息而无需重新编译,SCL 和 SDA 上的逻辑分析仪则能终结任何关于"线上到底跑了什么"的争论。
标识读对之后,按这个顺序加功能,每加一步就测试一次:先是通过正确子系统暴露的单次读取,再是中断,再是电源管理(运行时挂起),再是校准数据,最后才是可选模式。把寄存器表集中放在一处,不要在中断上下文里睡眠,并让驱动经得起被解绑再重新绑定:echo 1-0044 > /sys/bus/i2c/drivers/demo-sensor/unbind 是一个快速测试,能发现大部分资源处理上的错误。
把数据手册里的时序数字当作需要验证的限值,而不是前提:长线缆、弱上拉带来的缓慢边沿、会拉伸时钟的芯片,都只会在真实的板子上才显现。
驱动只是整个板级点亮中的一层。我们的 i.MX6 CAN 总线数据采集系统就是这样一个 Linux 项目,其中真正有意思的是驱动周围的数据通路:高负载下的采集、解析和存储。如果你需要为自己的硬件做驱动、设备树,或完整的板级点亮,请看我们的 Linux BSP 与驱动服务,更完整的全景见点亮指南。
联系我们获取定制化解决方案和报价