LINUX IIC总线驱动-设备框架

Linux I2C 驱动框架简介

IMX6ULL裸机篇中编写传感器AP3216C 驱动的时候,我们编写了四个文件:bsp_i2c.c、 bsp_i2c.h、bsp_ap3216c.c 和 bsp_ap3216c.h。编写IIC控制器驱动,bsp_i2c.c和bsp_i2c.h为IIC外设驱动。向外提供i2c_master_transfer函数;bsp_3216c.c和bsp_3216c.h为IIC设备驱动。其中前两个是 I.MX6U 的 IIC 接口驱动,后两个文 件是 AP3216C 这个 I2C 设备驱动文件。相当于有两部分驱动组成:

①、I2C 主机驱动

②、I2C 设备驱动

对于 I2C 主机驱动,一旦编写完成就不需要再做修改,其他的 I2C 设备直接调用主机驱动 提供的 API 函数完成读写操作即可。这个正好符合 Linux 的驱动分离与分层的思想,因此 Linux 内核也将 I2C 驱动分为两部分:

①、I2C 总线驱动,I2C 总线驱动就是 SOC 的 I2C 控制器驱动,也叫做 I2C 适配器驱动

②、I2C 设备驱动,I2C 设备驱动就是针对具体的 I2C 设备而编写的驱动

========================

I2C 总线驱动

首先来看一下 I2C 总线,在讲 platform 的时候就说过,platform 是虚拟出来的一条总线, 目的是为了实现总线、设备、驱动框架。对于 I2C 而言,不需要虚拟出一条总线,直接使用 I2C 总线即可。I2C 总线驱动重点是 I2C 适配器(也就是 SOC 的 I2C 接口控制器)驱动,这里要用到 两个重要的数据结构:i2c_adapteri2c_algorithm,Linux 内核将 SOC 的 I2C 适配器(控制器) 抽象成 i2c_adapter,i2c_adapter 结构体定义在include/linux/i2c.h 文件中,结构体内容如下:

struct i2c_adapter {
     struct module *owner;
     unsigned int class; /* classes to allow probing for */
     const struct i2c_algorithm *algo; /* 总线访问算法 */
     void *algo_data;
     /* data fields that are valid for all devices */
     struct rt_mutex bus_lock;
     int timeout; /* in jiffies */
     int retries;
     struct device dev; /* the adapter device */
     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;
 };

const struct i2c_algorithm *algo

i2c_algorithm 类型的指针变量 algo,对于一个 I2C 适配器,肯定要对外提供读 写 API 函数,设备驱动程序可以使用这些 API 函数来完成读写操作。i2c_algorithm 就是 I2C 适 配器与 IIC 设备进行通信的方法。 i2c_algorithm 结构体定义在 include/linux/i2c.h文件中,内容如下(删除条件编译):

 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);
     /* To determine what the adapter supports */
    u32 (*functionality) (struct i2c_adapter *);
......
 };

master_xfer 就是 I2C 适配器的传输函数,可以通过此函数来完成与 IIC 设备之 间的通信。smbus_xfer 就是 SMBUS 总线的传输函数。 综上所述,I2C 总线驱动,或者说 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 使用静态总线号。函数参数和返回值含义如下:
adapter 或 adap:要添加到 Linux 内核中的 i2c_adapter,也就是 I2C 适配器。返回值:0,成功;负值,失败。

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

void i2c_del_adapter(struct i2c_adapter * adap)

函数参数和返回值含义如下:

adap:要删除的 I2C 适配器。

返回值:无。

关于 I2C 的控制器或适配器驱动就到这里,一般 SOC 的 I2C 总线驱动都是由半 导体厂商编写的,比如 I.MX6U 的 I2C 适配器驱动 NXP 已经编写好了,这个不需要用户去编写。因此 I2C 总线驱动对我们这些 SOC 使用者来说是被屏蔽掉的,我们只要专注于 I2C 设备驱动即可。除非你是在半导体公司上班,工作内容就是写 I2C 适配器驱动。

===========================================

I2C 设备驱动

I2C 设备驱动重点关注两个数据结构:i2c_client 和 i2c_driver,根据总线、设备和驱动模型, I2C 总线上一小节已经讲了。还剩下设备和驱动,i2c_client 就是描述设备信息的,i2c_driver 描述驱动内容,类似于 platform_driver

i2c_client 结构体

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

struct i2c_client {
    unsigned short flags; /* 标志 */
    unsigned short addr; /* 芯片地址,7 位,存在低 7 位*/
......
    char name[I2C_NAME_SIZE]; /* 名字 */
    struct i2c_adapter *adapter; /* 对应的 I2C 适配器 */
    struct device dev; /* 设备结构体 */
    int irq; /* 中断 */
    struct list_head detected;
......
 };

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

i2c_driver 结构体

i2c_driver 类似 platform_driver,是我们编写 I2C 设备驱动重点要处理的内容,i2c_driver 结 构体定义在 include/linux/i2c.h 文件中,内容如下:

 struct i2c_driver {
     unsigned int class;

 /* Notifies the driver that a new bus has appeared. You should 
 * avoid using this, it will be removed in a near future.
 */
     int (*attach_adapter)(struct i2c_adapter *) __deprecated;

 /* Standard driver model interfaces */
     int (*probe)(struct i2c_client *, const struct i2c_device_id *);
     int (*remove)(struct i2c_client *);

 /* driver model interfaces that don't relate to enumeration */
     void (*shutdown)(struct i2c_client *);

 /* Alert callback, for example for the SMBus alert protocol.
 * The format and meaning of the data value depends on the 
 * protocol.For the SMBus alert protocol, there is a single bit 
 * of data passed as the alert response's low bit ("event 
 flag"). */
   
     void (*alert)(struct i2c_client *, unsigned int data);

 /* a ioctl like command that can be used to perform specific 
* functions with the device.
 */
     int (*command)(struct i2c_client *client, unsigned int cmd,
                   oid *arg);

     struct device_driver driver;
     const struct i2c_device_id *id_table;

 /* Device detection callback for automatic device creation */
     int (*detect)(struct i2c_client *, struct i2c_board_info *);
     const unsigned short *address_list;
     struct list_head clients;
 };

int (*probe)(struct i2c_client *, const struct i2c_device_id *),当 I2C 设备和驱动匹配成功以后 probe 函数就会执行,和 platform 驱动一样。

struct device_driver driverdevice_driver驱动结构体,如果使用设备树的话,需要设置 device_driverof_match_table 成员变量,也就是驱动的兼容(compatible)属性。

const struct i2c_device_id *id_table,id_table 是传统的、未使用设备树的设备匹配 ID 表。 对于我们 I2C 设备驱动编写人来说,重点工作就是构建 i2c_driver,构建完成以后需要向 Linux 内核注册这个 i2c_driver。i2c_driver 注册函数为 int 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_driver,i2c_add_driver 是一个宏,定义如下:

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

i2c_add_driver 就是对 i2c_register_driver 做了一个简单的封装,只有一个参数,就是要注册的 i2c_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_device_id 无设备树的时候匹配 ID 表

of_device_id 设备树所使用的匹配表

i2c_driver当 I2C 设备和 I2C 驱动匹配成功以后 probe 函数 就会执行,这些 和 platform 驱动一样,probe 函数里面基本就是标准的字符设备驱动那一套了

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,定义 在 drivers/i2c/i2c-core.c 文件,i2c_bus_type 内容如下:

struct bus_type i2c_bus_type = {
   .name = "i2c",
   .match = i2c_device_match,
   .probe = i2c_device_probe,
   .remove = i2c_device_remove,
   .shutdown = i2c_device_shutdown,
 };

.match就是 I2C 总线的设备和驱动匹配函数,在这里就是 i2c_device_match这个函数,此函数内容如下:

static int i2c_device_match(struct device *dev, struct device_driver *drv)
 {
    struct i2c_client *client = i2c_verify_client(dev);
    struct i2c_driver *driver;
if (!client)
    return 0;
 /* Attempt an OF style match */
if (of_driver_match_device(dev, drv))
    return 1;

 /* Then ACPI style match */
 if (acpi_driver_match_device(dev, drv))
     return 1;

     driver = to_i2c_driver(drv);
 /* match on an id table if there is one */
 if (driver->id_table)
     return i2c_match_id(driver->id_table, client) != NULL;
     return 0;
 }

of_driver_match_device(dev, drv)of_driver_match_device 函数用于完成设备树设备和驱动匹配。比较 I2C 设备节 点的 compatible 属性和 of_device_id 中的 compatible 属性是否相等,如果相当的话就表示 I2C 设备和驱动匹配。

acpi_driver_match_device(dev, drv)acpi_driver_match_device函数用于 ACPI 形式的匹配。

i2c_match_id(driver->id_table, client)i2c_match_id函数用于传统的、无设备树的 I2C 设备和驱动匹配过程。比较 I2C 设备名字和 i2c_device_id 的 name 字段是否相等,相等的话就说明 I2C 设备和驱动匹配。

相关推荐
钊兵12 小时前
数据库驱动免费下载(Oracle、Mysql、达梦、Postgresql)
数据库·mysql·postgresql·oracle·达梦·驱动
菜鸡00015 天前
Ubuntu 20 掉显卡驱动的解决办法
linux·经验分享·笔记·ubuntu·驱动
极客代码1 个月前
【Linux】设备驱动中的ioctl详解
linux·内核·驱动·设备驱动·iocto
不知火猪2 个月前
最新雷蛇鼠标键盘驱动Razer Synapse 4(雷云) 下载与安装
计算机外设·驱动·雷云·雷蛇驱动
楼兰公子2 个月前
相机主要调试参数
arm开发·驱动·camera·v4l2
极客代码3 个月前
【Linux】【字符设备驱动】深入解析
linux·驱动开发·unix·驱动·字符设备驱动
沐多3 个月前
linux模拟HID USB设备及wireshark USB抓包配置
驱动
极客代码3 个月前
【Linux】内核驱动模块
linux·内核·内核模块·unix·驱动
郁大锤3 个月前
linux alsa-lib snd_pcm_open函数源码分析(四)
linux·音频·pcm·源码分析·驱动·alsa
昵称p3 个月前
如何解决不能将开发板连接到虚拟机的问题(连接显示灰色,不能选中)
驱动·虚拟机无法连接开发板