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

目录

[I2C 总线驱动](#I2C 总线驱动)

i2c_adapter结构体

[i2c_algorithm 结构体](#i2c_algorithm 结构体)

注册函数

删除函数

[I2C 设备驱动](#I2C 设备驱动)

[i2c_client 结构体](#i2c_client 结构体)

[i2c_driver 结构体](#i2c_driver 结构体)

i2c_register_driver函数

i2c_add_driver宏

[i2c_del_driver 函数](#i2c_del_driver 函数)

[I2C 设备和驱动匹配过程](#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结构体内容如下:

cpp 复制代码
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 文件中,内容如下(删除条件编译):

cpp 复制代码
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,这两个函数的原型如下:

cpp 复制代码
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 函数即可,函数原型如下:

cpp 复制代码
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文件中,内容如下:

cpp 复制代码
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 文件中,内容如下:

cpp 复制代码
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)属性。

cpp 复制代码
 struct device_driver driver; 

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

i2c_register_driver函数

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

cpp 复制代码
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 是一个宏,定义如下:

cpp 复制代码
#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 函数,此函数原型如下:

cpp 复制代码
void i2c_del_driver(struct i2c_driver *driver)
  • driver:要注销的 i2c_driver。

i2c_driver 的注册示例代码如下:

cpp 复制代码
/* 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 函数:

cpp 复制代码
//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文件, 内容如下:

cpp 复制代码
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内容如下:

cpp 复制代码
/**
 * 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 设备和驱动匹配。
相关推荐
凌肖战4 小时前
编写Linux下设备驱动时两种方案:内核态驱动开发和用户态驱动开发
linux·驱动开发
Wy_编程6 小时前
VS中创建Linux项目
linux
luck_lin7 小时前
linux添加新硬盘挂载分区和数据迁移
linux·运维·分区扩容
iFulling7 小时前
【云原生】CentOS安装Kubernetes+Jenkins
linux·云原生·kubernetes·centos·jenkins
喜欢你,还有大家9 小时前
Linux笔记10——shell编程基础-4
linux·运维·服务器·笔记
不懂机器人9 小时前
linux编程----网络通信(TCP)
linux·服务器·tcp/ip
tanyongxi6610 小时前
简易shell
linux·运维·服务器
vortex510 小时前
Python包管理与安装机制详解
linux·python·pip
zcz160712782110 小时前
CentOS 7 服务器初始化完整流程
linux·服务器·centos