linux内核i2c ioctl,linux驱动之i2c框架

一、前言

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等

三、参考链接

你可能感兴趣的:(linux内核i2c,ioctl)