一、前言
I2C总线 是一种常用的总线协议,在设备中经常看到,比如 sensor、陀螺仪等都是使用 I2C总线。而 Linux内核 也提供成熟的 I2C框架,工程师可以根据硬件特性直接使用该框架编写驱动程序。本文将着重阐述 Linux内核 关于 I2C总线 的一些概念和实现
PS:本文默认读者已经熟悉I2C协议,请不了解I2C协议的读者自行查阅其他资料了解
二、I2C总线
2.1 重要概念
在了解 Linux内核 的 I2C框架 之前,需要对框架的一些重要概念进行认识,这样有助于理解框架的设计及思路,加深印象。
i2c_bus_type:i2c_bus_type 是 Linux内核设备框架 中的 总线。该 变量 是一个全局变量,用于 匹配和删除I2C设备和I2C驱动 ,并负责提供 匹配规则。i2c_bus_type 的 i2c_device_match 会查看 驱动 和 设备 是否 匹配,如果匹配则通过 i2c_device_probe 调用 驱动的probe函数。
其代码如下:
struct bus_type i2c_bus_type = {
.name = "i2c",
.match = i2c_device_match,
.probe = i2c_device_probe,
.remove = i2c_device_remove,
.shutdown = i2c_device_shutdown,
};
i2c_adapter:i2c_adapter 称为 I2C适配器。所谓 I2C适配器 就是 Soc 上的 I2C控制器,而 i2c_adapter 就是 硬件I2C控制器 的 驱动实现,即实现了 CPU通过I2C控制器与外界进行数据交换
struct i2c_adapter {
/* I2C适配器的通信方法 */
const struct i2c_algorithm *algo;
/* I2C适配器的device结构体,表明其也是一个设备 */
struct device dev;
};
i2c_algorithm :i2c_algorithm 是 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 *);
};
i2c_client:i2c_client 就是 I2C设备,该结构体描述了 I2C设备硬件信息
struct i2c_client {
/* 设备I2C地址 */
unsigned short addr; /* chip address - NOTE: 7bit */
/* 设备名称 */
char name[I2C_NAME_SIZE];
/* I2C适配器 */
struct i2c_adapter *adapter; /* the adapter we sit on */
/* device结构体,表明其为一个设备 */
struct device dev; /* the device structure */
};
i2c_driver:i2c_driver 就是 I2C驱动程序,就是 I2C硬件设备端的实现。一般挂接在 I2C适配器 上,并通过 I2C适配器 与 CPU 交换数据。
struct i2c_driver {
/* 驱动的probe函数 */
int (*probe)(struct i2c_client *, const struct i2c_device_id *);
/* 驱动的remove函数 */
int (*remove)(struct i2c_client *);
/* I2C驱动的device_driver结构体,表明其也是一个设备驱动 */
struct device_driver driver;
/* 驱动ID table,用于匹配 */
const struct i2c_device_id *id_table;
/* 该驱动拥有的I2C地址 */
const unsigned short *address_list;
/* 该驱动拥有的I2C设备链表 */
struct list_head clients;
};
笔者粗略的画了下他们之间的关系图,如下所示:
关系图
虚线表示对应关系,实现表示数据
硬件信息 通过 设备树 解析到了 i2c_client
i2c_driver 的 匹配信息 在 驱动程序 中填充。
i2c_client 和 i2c_driver 通过 i2c_bus_type 进行匹配,如果成功 i2c_bus_type 调用 i2c_driver 的 probe函数
i2c_driver 通过 i2c_adapter 向外发送数据
2.2 代码分层
了解完主要的数据结构,下面看看 I2C框架 的主体代码组成,其源码目录在 /drivers/i2c。下分多个文件和文件夹,包括 I2C框架代码、不同体系结构的I2C驱动代码 等。
I2C框架的主体代码和目录 分别如下:
i2c-core.c:I2C框架核心功能源码,该文件使得 I2C框架 实现了 硬件 与 软件 的分层。
i2c-dev.c:该源码实现了 I2C适配器设备文件的功能,给 I2C适配器 分配一个 设备号 并将其实现为 /dev目录 下的 设备。用户可以通过该 设备文件 直接在 应用层 操作 Soc的I2C控制器
busses:不同 Soc的I2C控制器 的驱动代码。
2.3 代码讲解
2.3.1 I2C驱动注册
编写 I2C设备驱动 时一般按照下面几个步骤进行:
使用 i2c_add_driver 添加驱动
实现 I2C设备驱动操作集,比如 probe、remove等
实现 I2C设备文件操作集,比如 read、write等
实现设备调用 i2c_add_driver函数 有多种方式,如下:
使用 宏module_i2c_driver 直接添加驱动,该宏会在 驱动装载 时自动调用 i2c_add_driver函数,其本质是注册 驱动初始函数 和 驱动去初始化函数。
与一般设备一样,通过设备树或多种方法 手动调用i2c_add_driver
i2c_add_driver 调用图谱如下:
i2c_add_driver(宏)
->i2c_register_driver(函数)
->driver_register(想内核注册驱动)
->bus_add_driver(在i2c_bus_type上注册驱动)
->i2c_for_each_dev(driver, __process_new_driver);(为每个驱动调用__process_new_driver函数)
->__process_new_driver
->i2c_do_add_adapter(加驱动添加到对应的I2C适配器)
->i2c_detect(I2C适配器探测是否存在驱动所对应的I2C设备)
->i2c_detect_address(I2C适配器使用地址对I2C设备进行探测)
->i2c_new_device(生成新的i2c_client(用于描述I2C设备硬件信息),并挂接在I2C驱动上)
代码如下:
#define module_i2c_driver(__i2c_driver) \
module_driver(__i2c_driver, i2c_add_driver, \
i2c_del_driver)
#define i2c_add_driver(driver) \
i2c_register_driver(THIS_MODULE, driver)
struct bus_type i2c_bus_type = {
.name = "i2c",
.match = i2c_device_match,
.probe = i2c_device_probe,
.remove = i2c_device_remove,
.shutdown = i2c_device_shutdown,
};
int i2c_register_driver(struct module *owner, struct i2c_driver *driver)
{
....
/* 注册i2c_driver的总线为i2c_bus_type */
driver->driver.bus = &i2c_bus_type;
/* 初始化i2c_driver的i2c_client链表,一个driver可以有多个client,挂接在该链表上 */
INIT_LIST_HEAD(&driver->clients);
....
/* 注册驱动 */
res = driver_register(&driver->driver);
....
/* 对i2c_driver上的每一个i2c_client都调用__process_new_driver */
i2c_for_each_dev(driver, __process_new_driver);
return 0;
}
下面分为 2个 阶段来阐述注册过程:
driver_register:注册驱动
i2c_for_each_dev:遍历设备并调用 __process_new_driver 创建 i2c_client
下面先看看 driver_register 的代码流程:
int driver_register(struct device_driver *drv)
{
......
/* 将驱动添加到总线上 */
ret = bus_add_driver(drv);
......
return ret;
}
int bus_add_driver(struct device_driver *drv)
{
/* 将驱动添加到总线的驱动链表(bus->p->klist_drivers) */
klist_add_tail(&priv->knode_bus, &bus->p->klist_drivers);
......
if (drv->bus->p->drivers_autoprobe) {
if (driver_allows_async_probing(drv)) {
......
} else {
error = driver_attach(drv);
......
}
}
......
return 0;
}
int driver_attach(struct device_driver *drv)
{
/* 遍历总线上的所有驱动并调用__driver_attach */
return bus_for_each_dev(drv->bus, NULL, drv, __driver_attach);
}
int bus_for_each_dev(struct bus_type *bus, struct device *start, void *data, fn)
{
/*
遍历i2c_bus_type总线的所有设备链表(bus->p->klist_devices)的所有设备,执行fn函数
这里的设备就是i2c_client中的struct device成员
在设备树解析时会根据i2c节点创建i2c_client,并将其struct device成员链入i2c_bus_type的bus->p->klist_devices链表
*/
while ((dev = next_device(&i)) && !error)
error = fn(dev, data);
}
static int __driver_attach(struct device *dev, void *data)
{
struct device_driver *drv = data;
int ret;
/* 匹配设备和驱动,如果不成功则返回 */
ret = driver_match_device(drv, dev);
if (ret == 0) {
/* no match */
return 0;
}
......
/* 匹配设备和驱动成功,调用probe函数 */
if (!dev->driver)
driver_probe_device(drv, dev);
......
return 0;
}
static inline int driver_match_device(struct device_driver *drv,
struct device *dev)
{
/* 调用i2c_bus_type总线的macth函数,即i2c_device_match函数 */
return drv->bus->match ? drv->bus->match(dev, drv) : 1;
}
static int i2c_device_match(struct device *dev, struct device_driver *drv)
{
......
driver = to_i2c_driver(drv);
/* 调用i2c_match_id进行配 */
if (i2c_match_id(driver->id_table, client))
return 1;
return 0;
}
const struct i2c_device_id *i2c_match_id(const struct i2c_device_id *id, const struct i2c_client *client)
{
/* 匹配驱动的名称和设备名称,并返回结果 */
while (id->name[0]) {
if (strcmp(client->name, id->name) == 0)
return id;
id++;
}
}
int driver_probe_device(struct device_driver *drv, struct device *dev)
{
......
/* 如果匹配到设备则调用probe函数 */
ret = really_probe(dev, drv);
......
}
static int really_probe(struct device *dev, struct device_driver *drv)
{
/* 调用i2c_bus_type的probe函数,即i2c_device_probe函数 */
if (dev->bus->probe) {
ret = dev->bus->probe(dev);
if (ret)
goto probe_failed;
}
}
static int i2c_device_probe(struct device *dev)
{
/*
调用i2c驱动的probe函数
*/
driver->probe(client, i2c_match_id(driver->id_table, client));
}
到了这里,driver_register 的流程基本结束,下面主要执行 i2c_for_each_dev,代码流程如下:
int i2c_for_each_dev(void *data, fn)
{
......
/* 对i2c_bus_type总线上的设备进行遍历 */
res = bus_for_each_dev(&i2c_bus_type, NULL, data, fn);
......
}
int bus_for_each_dev(struct bus_type *bus, struct device *start, void *data, int fn)
{
......
/*
找出挂在bus->p->klist_devices链表上的首个设备(struct device)的迭代器。
这里可能有读者有疑问,这些struct device什么时链入bus->p->klist_devices的呢
在设备树解析期间,如果设备树的i2c节点有设备节点,则会调用i2c_new_device。
该函数会创建一个i2c_client,并将该i2c_client的struct device成员链入bus->p->klist_devices链表
其中的bus就是i2c_bus_type
*/
klist_iter_init_node(&bus->p->klist_devices, &i,
(start ? &start->p->knode_bus : NULL));
/* 遍历总线上的设备链表(bus->p->klist_devices)的所有设备,调用fn函数 */
while ((dev = next_device(&i)) && !error)
error = fn(dev, data);
......
}
static int __process_new_driver(struct device *dev, void *data)
{
if (dev->type != &i2c_adapter_type)
return 0;
/*
根据函数传递进来的参数可知,data参数就是i2c_driver结构体
而to_i2c_adapter(dev)用于找出该设备对应的i2c_adapter。
这里也是同理,在设备树解析时,会根据设备所在的i2c节点(设备树的i2c节点对应一个i2c_控制器)
将设备链入对应的i2c_adapter结构体
*/
return i2c_do_add_adapter(data, to_i2c_adapter(dev));
}
static int i2c_do_add_adapter(struct i2c_driver *driver,
struct i2c_adapter *adap)
{
/* 使用对应的i2c_adapter探测驱动所对应的设备是否存在 */
i2c_detect(adap, driver);
......
return 0;
}
static int i2c_detect(struct i2c_adapter *adapter, struct i2c_driver *driver)
{
......
/* 获取驱动的I2C地址链表 */
address_list = driver->address_list;
/*
如果i2c_driver没实现detect函数,那么将会在这里返回
有些i2c驱动没有实现detect函数,所以注册流程到这里就结束了
*/
if (!driver->detect || !address_list)
return 0;
for (i = 0; address_list[i] != I2C_CLIENT_END; i += 1) {
......
/* i2c_detect_address会根据地址链表去探测是否存在设备 */
temp_client->addr = address_list[i];
err = i2c_detect_address(temp_client, driver);
......
}
}
static int i2c_detect_address(struct i2c_client *temp_client,
struct i2c_driver *driver)
{
struct i2c_board_info info;
struct i2c_adapter *adapter = temp_client->adapter;
int addr = temp_client->addr;
int err;
......
memset(&info, 0, sizeof(struct i2c_board_info));
info.addr = addr;
/* 调用驱动的detecr函数 */
err = driver->detect(temp_client, &info);
if (err) {
/* 失败就返回-ENODEV */
return err == -ENODEV ? 0 : err;
}
/* Consistency check */
if (info.type[0] == '\0') {
......
} else {
/* 如果探测成功则使用i2c_new_device创建client并添加到driver的clients链表 */
client = i2c_new_device(adapter, &info);
if (client)
list_add_tail(&client->detected, &driver->clients);
else
dev_err(&adapter->dev, "Failed creating %s at 0x%02x\n",
info.type, info.addr);
}
return 0;
}
有些 I2C驱动 并不会实现 detect函数,所以在知道到 driver_register 时已经完成大部分工作,在 执行 i2c_for_each_dev 时就已经早早退出了。
前面的代码多次提到过 i2c_client结构体 和 struct device结构,从前面的代码讲解中我们已经知道 设备树解析 时会根据 I2C节点 的信息创建对应的 i2c_client 和 struct device,那么这里也提一下如何在 设备树 中添加 I2C设备节点信息
在 Documentation/devicetree/bindings/i2c 目录下的 i2c.txt文档 中可以查看 i2c_adapter 的设备节点信息,而在同一目录下存在 多个Soc平台 的 i2c适配器说明。
I2C设备 一般是作为 slaver(从机) 存在,而一般的 I2C从机节点信息 有以下属性:
compatible:用于 驱动匹配
reg:I2C设备 的总线地址
下面为 设备树 代码例子:
i2c0@0xXXXXXXXX {
/* i2c控制器的硬件信息 */
......
/* i2c设备节点信息 */
i2c_test: i2c_test@34{
compatible = "xxxx";
/* reg表明该I2C设备的地址为0x34 */
reg = <0x34>;
};
};
2.3.2 I2C设备注册
在 I2C驱动注册 中使用的是默认的 自动注册设备,也就是通过 设备树,由操作系统帮我们完成 设备的生成和注册。
在解析 I2C设备节点 时,一般调用的是:
of_i2c_register_device
of_i2c_register_devices
它们最终都会调用到 i2c_new_device 来创建 I2C设备。
如果在某些场景,比如 设备树 没有 I2C设备节点, 想 手动注册I2C设备 ,那么就可以通过手动调用 i2c_new_device 来创建和注册 I2C设备。
其函数调用图谱如下:
->i2c_new_device
->device_register
->device_add
->bus_add_device
->bus_probe_device
->__device_attach
->driver_match_device
->driver_probe_device
struct i2c_client* i2c_new_device(struct i2c_adapter *adap, struct i2c_board_info const *info)
{
......
/* 创建完i2c_client后对其进行一列的初始化 */
client->adapter = adap;
client->dev.platform_data = info->platform_data;
client->flags = info->flags;
client->addr = info->addr;
client->irq = info->irq;
strlcpy(client->name, info->type, sizeof(client->name));
......
/* 设备i2c_client的struct device成员 */
client->dev.parent = &client->adapter->dev;
client->dev.bus = &i2c_bus_type;
client->dev.type = &i2c_client_type;
client->dev.of_node = info->of_node;
client->dev.fwnode = info->fwnode;
......
i2c_dev_set_name(adap, client);
......
/* 注册shebei */
status = device_register(&client->dev);
......
return client;
}
int device_register(struct device *dev)
{
device_initialize(dev);
/* 添加设备 */
return device_add(dev);
}
int device_add(struct device *dev)
{
......
/* 将设备添加是bus上 */
error = bus_add_device(dev);
......
/* 探测bus上是否有与设备匹配的驱动 */
bus_probe_device(dev);
......
}
int bus_add_device(struct device *dev)
{
struct bus_type *bus = bus_get(dev->bus);
......
if (bus) {
/* 将设备添加到bus的klist_devices链表欧尚,该成员在注册驱动时会使用到。用于匹配 */
klist_add_tail(&dev->p->knode_bus, &bus->p->klist_devices);
}
return 0;
}
void bus_probe_device(struct device *dev)
{
struct bus_type *bus = dev->bus;
struct subsys_interface *sif;
......
if (bus->p->drivers_autoprobe)
/* device_initial_probe直接调用__device_attach */
device_initial_probe(dev);
......
}
static int __device_attach(struct device *dev, bool allow_async)
{
......
/* 对bus上的每个设备都调用__device_attach_driver */
ret = bus_for_each_drv(dev->bus, NULL, &data, __device_attach_driver);
......
}
static int __device_attach_driver(struct device_driver *drv, void *_data)
{
......
/* 匹配设备与驱动 */
ret = driver_match_device(drv, dev);
......
/* 匹配成功则调用probe */
return driver_probe_device(drv, dev);
}
2.4 驱动接口及步骤
2.4.1 I2C框架接口
增加/删除I2C适配器:
i2c_add_adapter
i2c_del_adapter
增加/删除I2C驱动:
i2c_add_driver
i2c_del_driver
I2C传输、发送和接收
i2c_transfer(adap, msgs, num):用于 I2C适配器 和 设备 之间的 一组 消息交互。注意其传入的实例为 I2C适配器
i2c_master_recv:用于 主机接收,内部调用 i2c_transfer。注意其传入的实例为 I2C设备(i2c_client)
i2c_master_send(client, buf, count):用于 主机发送,内部调用 i2c_transfer。注意其传入的实例为 I2C设备(i2c_client)
2.4.2 I2C步骤
对 I2C驱动 的移植和编写主要有 2部分:
I2C适配器:完成 I2C适配器 的硬件驱动
探测及初始化 I2C适配器,如 申请寄存器地址、申请中断 等
驱动 I2C适配器 产生信号及 中断处理
提供 I2C适配器 的 algorithm(即通信方法)
使用 i2c_add_adapter 添加 I2C适配器
I2C驱动:完成 I2C设备外设 的硬件驱动
实现 i2c_driver 的接口,如 probe、remove、suspend、resume 等
填充 i2c_device_id表 并赋值给 i2c_driver
实现 I2C设备 的具体方法,比如 read、write、ioctl等
三、参考链接