> 技术文档 > imx6ull-驱动开发篇42——Linux I2C 驱动框架简介

imx6ull-驱动开发篇42——Linux I2C 驱动框架简介

目录

I2C 总线驱动

i2c_adapter结构体

i2c_algorithm 结构体

注册函数

删除函数

I2C 设备驱动

i2c_client 结构体

i2c_driver 结构体

i2c_register_driver函数

i2c_add_driver宏

i2c_del_driver 函数

I2C 设备和驱动匹配过程

i2c_bus_type结构体

i2c_device_match函数


Linux内核将 I2C 驱动分为两部分:

  • I2C 总线驱动, I2C 总线驱动就是 SOC 的 I2C 控制器驱动,也叫做 I2C 适配器驱动。
  • I2C 设备驱动, I2C 设备驱动就是针对具体的 I2C 设备而编写的驱动。

I2C 总线驱动

I2C 总线驱动重点是 I2C 适配器驱动,这里要用到两个重要的数据结构: i2c_adapter 和 i2c_algorithm。

i2c_adapter结构体

Linux 内核将 SOC 的 I2C 适配器(控制器)抽象成 i2c_adapter, i2c_adapter 结构体定义在 include/linux/i2c.h 文件中。

i2c_adapter结构体内容如下:

struct i2c_adapter { struct module *owner; // 拥有该适配器的模块,用于引用计数 unsigned int class;  // 允许探测的设备类 const struct i2c_algorithm *algo; // 总线访问算法(关键操作函数指针) void *algo_data;  // 算法私有数据 /* 所有设备通用的数据字段 */ struct rt_mutex bus_lock; // 总线互斥锁(实时互斥锁,防止并发访问) int timeout; // 超时时间(以jiffies为单位) int retries; // 操作重试次数 struct device dev;  // 适配器对应的设备结构体 int nr; // 适配器编号 char name[48]; // 适配器名称 struct completion dev_released; // 设备释放完成量 /* 用户空间客户端管理 */ struct mutex userspace_clients_lock; // 用户空间客户端列表锁 struct list_head userspace_clients; // 用户空间客户端链表 struct i2c_bus_recovery_info *bus_recovery_info; // 总线恢复信息(错误恢复机制) const struct i2c_adapter_quirks *quirks; // 适配器特殊行为/变通方案};

其中,i2c_algorithm 类型的指针变量 algo,对于一个 I2C 适配器,肯定要对外提供读写 API 函数,设备驱动程序可以使用这些 API 函数来完成读写操作。

i2c_algorithm 就是 I2C 适配器与 IIC 设备进行通信的方法。

i2c_algorithm 结构体

i2c_algorithm 结构体定义在 include/linux/i2c.h 文件中,内容如下(删除条件编译):

struct i2c_algorithm {...... /* I2C 传输函数(核心操作) */ int (*master_xfer)(struct i2c_adapter *adap, // 关联的适配器struct i2c_msg *msgs, // 消息数组(包含读写操作)int num);  // 消息数量 /* SMBus 传输函数 */ int (*smbus_xfer)(struct i2c_adapter *adap, // 关联的适配器  u16 addr,  // 设备地址(7位或10位)  unsigned short flags, // 标志位(如I2C_M_TEN表示10位地址)  char read_write, // 读写方向(I2C_SMBUS_READ/WRITE)  u8 command,  // SMBus命令字节  int size,  // 数据大小(决定SMBus协议类型)  union i2c_smbus_data *data); // 传输数据(读写缓冲区) /* 查询适配器支持的功能 */ u32 (*functionality)(struct i2c_adapter *); // 返回I2C_FUNC_*标志位组合 // 例如:是否支持SMBus、10位地址等......};
  • master_xfer 就是 I2C 适配器的传输函数,可以通过此函数来完成与 IIC 设备之间的通信。
  • smbus_xfer 就是 SMBUS 总线的传输函数。

I2C 总线驱动,主要工作就是初始化 i2c_adapter 结构体变量,然后设置 i2c_algorithm 中的 master_xfer 函数。

注册函数

通过 i2c_add_numbered_adapteri2c_add_adapter 这两个函数向系统注册设置好的 i2c_adapter,这两个函数的原型如下:

int i2c_add_adapter(struct i2c_adapter *adapter)int i2c_add_numbered_adapter(struct i2c_adapter *adap)

 i2c_add_adapter 使用动态的总线号,而 i2c_add_numbered_adapter使用静态总线号。

这两个函数的对比如下:

删除函数

如果要删除 I2C 适配器的话使用 i2c_del_adapter 函数即可,函数原型如下:

void i2c_del_adapter(struct i2c_adapter * adap)
  • adap:要删除的 I2C 适配器。

一般 SOC 的 I2C 总线驱动都是由半导体厂商编写的,比如 I.MX6U 的 I2C 适配器驱动 NXP 已经编写好了,不需要用户去编写。

I2C 设备驱动

I2C 设备驱动主要是两个数据结构: i2c_client 和 i2c_driver,i2c_client 就是描述设备信息的, i2c_driver 描述驱动内容,类似于 platform_driver。

i2c_client 结构体

i2c_client 结构体定义在 include/linux/i2c.h 文件中,内容如下:

struct i2c_client { unsigned short flags; /* 标志位(如I2C_CLIENT_TEN表示10位地址)*/ unsigned short addr; /* 设备地址(7位,存储于低7位)*/...... char name[I2C_NAME_SIZE]; /* 设备名称(匹配驱动用,如\"lm75\")*/ struct i2c_adapter *adapter; /* 所属的I2C适配器(指向父控制器)*/ struct device dev; /* 内嵌的设备结构体(用于设备模型)*/ int irq;/* 设备使用的中断号(若无则为0)*/ struct list_head detected; /* 链表节点(用于管理已探测到的设备)*/......};

一个设备对应一个 i2c_client,每检测到一个 I2C 设备就会给这个 I2C 设备分配一个i2c_client。

i2c_driver 结构体

i2c_driver 结构体定义在 include/linux/i2c.h 文件中,内容如下:

struct i2c_driver { unsigned int class; /* 驱动支持的设备类别(用于旧式匹配机制) */ /* 已废弃:通知驱动有新总线出现(避免使用,未来会移除) */ int (*attach_adapter)(struct i2c_adapter *) __deprecated; /* 标准驱动模型接口 */ int (*probe)(struct i2c_client *, const struct i2c_device_id *); /* 设备探测回调(必须实现) */ int (*remove)(struct i2c_client *); /* 设备移除回调(必须实现) */ /* 非枚举相关的驱动模型接口 */ void (*shutdown)(struct i2c_client *); /* 系统关机时调用的设备关闭回调 */ /* 警报回调(例如SMBus警报协议) */ void (*alert)(struct i2c_client *, unsigned int data); /* 参数说明: - data: 协议相关数据(SMBus警报协议中为低比特位\"event flag\") */ /* 类ioctl命令,用于执行设备特定功能 */ int (*command)(struct i2c_client *client, unsigned int cmd, void *arg); /* 参数说明: - cmd: 自定义命令号 - arg: 命令参数指针 */ struct device_driver driver; /* 内嵌的标准设备驱动结构体(包含.name/.owner等) */ const struct i2c_device_id *id_table; /* 支持的设备ID表(用于驱动匹配) */ /* 自动设备创建的检测回调 */ int (*detect)(struct i2c_client *, struct i2c_board_info *); /* 功能: - 检测设备并填充board_info(用于动态设备注册) */ const unsigned short *address_list; /* 探测时的设备地址列表(用于detect回调) */ struct list_head clients; /* 该驱动管理的客户端设备链表 */};

其中,有一个device_driver 驱动结构体,如果使用设备树的话,需要设置 device_driver 的of_match_table 成员变量,也就是驱动的兼容(compatible)属性。

 struct device_driver driver; 

构建 i2c_driver完成以后,需要向Linux 内核注册这个 i2c_driver。

i2c_register_driver函数

i2c_driver 注册函数为 i2c_register_driver,此函数原型如下:

int i2c_register_driver(struct module *owner, struct i2c_driver *driver)
  • owner: 一般为 THIS_MODULE。
  • driver:要注册的 i2c_driver。
  • 返回值: 0,成功;负值,失败。

i2c_add_driver宏

i2c_add_driver 也常常用于注册 i2c_driver, i2c_add_driver 是一个宏,定义如下:

#define i2c_add_driver(driver) \\ i2c_register_driver(THIS_MODULE, driver)

i2c_add_driver 就是对 i2c_register_driver 做了一个简单的封装,只有一个参数,就是要注册的 i2c_driver。

i2c_del_driver 函数

注销 I2C 设备驱动的时候,需要将前面注册的 i2c_driver 从 Linux 内核中注销掉,需要用到i2c_del_driver 函数,此函数原型如下:

void i2c_del_driver(struct i2c_driver *driver)
  • driver:要注销的 i2c_driver。

i2c_driver 的注册示例代码如下:

/* i2c 驱动的 probe 函数 */static int xxx_probe(struct i2c_client *client, const struct i2c_device_id *id){ /* 函数具体程序 */ return 0;}/* i2c 驱动的 remove 函数 */static int xxx_remove(struct i2c_client *client){ /* 函数具体程序 */ return 0;}/* 传统匹配方式 ID 列表 */static const struct i2c_device_id xxx_id[] = { {\"xxx\", 0}, {}};/* 设备树匹配列表 */static const struct of_device_id xxx_of_match[] = { { .compatible = \"xxx\" }, { /* Sentinel */ }};/* i2c 驱动结构体 */static struct i2c_driver xxx_driver = { .probe = xxx_probe, .remove = xxx_remove, .driver = { .owner = THIS_MODULE, .name = \"xxx\", .of_match_table = xxx_of_match, }, .id_table = xxx_id,};/* 驱动入口函数 */static int __init xxx_init(void){ int ret = 0; ret = i2c_add_driver(&xxx_driver); return ret;}/* 驱动出口函数 */static void __exit xxx_exit(void){ i2c_del_driver(&xxx_driver);}module_init(xxx_init);module_exit(xxx_exit);

I2C 设备和驱动匹配过程

I2C 设备和驱动的匹配过程是由 I2C 核心来完成的, drivers/i2c/i2c-core.c 就是 I2C 的核心部分文件。

I2C 核心提供了一些与具体硬件无关的 API 函数:

//i2c_adapter 注册/注销函数int i2c_add_adapter(struct i2c_adapter *adapter)int i2c_add_numbered_adapter(struct i2c_adapter *adap)void i2c_del_adapter(struct i2c_adapter * adap)// i2c_driver 注册/注销函数int i2c_register_driver(struct module *owner, struct i2c_driver *driver)int i2c_add_driver (struct i2c_driver *driver)void i2c_del_driver(struct i2c_driver *driver)

设备和驱动的匹配过程也是由 I2C 总线完成的, I2C 总线的数据结构为 i2c_bus_type,

i2c_bus_type结构体

i2c_bus_type结构体定义在 drivers/i2c/i2c-core.c 文件, 内容如下:

struct bus_type i2c_bus_type = { .name = \"i2c\",  // 总线名称(出现在/sys/bus/i2c) /* 核心回调函数 */ .match = i2c_device_match, // 设备与驱动匹配的核心逻辑 .probe = i2c_device_probe, // 设备探测入口(最终调用驱动的probe) .remove = i2c_device_remove, // 设备移除入口(最终调用驱动的remove) .shutdown = i2c_device_shutdown,// 系统关机时调用的设备关闭处理};

.match 就是 I2C 总线的设备和驱动匹配函数,在这里就是 i2c_device_match 这个函数。

i2c_device_match函数

i2c_device_match内容如下:

/** * i2c_device_match - 匹配I2C设备与驱动的核心函数 * @dev: 待匹配的设备(struct device指针) * @drv: 待匹配的驱动(struct device_driver指针) * * 返回值:匹配成功返回1,失败返回0 */static int i2c_device_match(struct device *dev, struct device_driver *drv){ /* 验证设备是否为有效的I2C客户端 */ struct i2c_client *client = i2c_verify_client(dev); struct i2c_driver *driver; /* 如果不是I2C客户端设备立即返回不匹配 */ if (!client) return 0; /* 第一阶段:设备树(OF)风格匹配(最高优先级) */ if (of_driver_match_device(dev, drv)) return 1; /* 第二阶段:ACPI风格匹配 */ if (acpi_driver_match_device(dev, drv)) return 1; /* 获取I2C驱动结构体 */ driver = to_i2c_driver(drv); /* 第三阶段:ID表匹配(传统匹配方式) */ if (driver->id_table) return i2c_match_id(driver->id_table, client) != NULL; /* 所有匹配方式都失败 */ return 0;}
  • of_driver_match_device 函数,用于完成设备树设备和驱动匹配。比较 I2C 设备节点的 compatible 属性和 of_device_id 中的 compatible 属性是否相等,如果相当的话就表示 I2C设备和驱动匹配。
  • acpi_driver_match_device 函数,用于 ACPI 形式的匹配。
  • i2c_match_id 函数,用于传统的、无设备树的 I2C 设备和驱动匹配过程。比较 I2C设备名字和 i2c_device_id 的 name 字段是否相等,相等的话就说明 I2C 设备和驱动匹配。