zoukankan      html  css  js  c++  java
  • Linux I2C总线(一)I2C驱动框架

    copy from:https://blog.csdn.net/weixin_42462202/article/details/100083025

    文章目录
    Linux I2C总线(一)I2C驱动框架
    一、Linux I2C驱动的主要对象
    1.1 I2C总线
    1.2 I2C设备
    1.3 I2C驱动
    1.4 I2C适配器
    二、Linux I2C驱动框架
    三、I2C驱动框架源码剖析
    3.1 注册I2C设备
    3.2 注册I2C驱动
    3.3 I2C适配器的构建
    3.4 I2C数据传输
    一、Linux I2C驱动的主要对象
    1.1 I2C总线
    I2C总线用于管理I2C设备和I2C驱动,维护一个设备链表和驱动链表,定义了设备和驱动的匹配规则,定义了匹配成功后的行为,其在内核中的定义如下

    struct bus_type i2c_bus_type = {
    .name = "i2c",
    .match = i2c_device_match, //匹配规则
    .probe = i2c_device_probe, //匹配后的行为
    .remove = i2c_device_remove,
    .shutdown = i2c_device_shutdown,
    .pm = &i2c_device_pm_ops,
    };

    1.2 I2C设备
    I2C设备描述了I2C设备的硬件信息,例如I2C设备的地址、I2C设备在接在哪一个I2C控制器上,其结构体定义如下

    struct i2c_client {
    unsigned short addr; //设备地址
    char name[I2C_NAME_SIZE]; //设备名称
    struct i2c_adapter *adapter; //适配器,指I2C控制器
    struct i2c_driver *driver; //设备对应的驱动
    struct device dev; //表明这是一个设备
    int irq; //中断号
    struct list_head detected;
    };

    1.3 I2C驱动
    I2C驱动是I2C设备的驱动程序,用于匹配I2C设备,其结构体定义如下

    struct i2c_driver {
    int (*probe)(struct i2c_client *, const struct i2c_device_id *); //probe函数
    struct device_driver driver; //表明这是一个驱动
    const struct i2c_device_id *id_table; //要匹配的从设备信息(名称)
    int (*detect)(struct i2c_client *, struct i2c_board_info *); //设备探测函数
    const unsigned short *address_list; //设备地址
    struct list_head clients; //设备链表
    ...
    };

    1.4 I2C适配器
    I2C适配器是SOC上的I2C控制器的软件抽象,可以通过其定义的算法向硬件设备传输数据,其结构体定义如下

    struct i2c_adapter {
    unsigned int id;
    unsigned int class;
    const struct i2c_algorithm *algo; //算法
    void *algo_data;
    ...
    struct device dev; /* the adapter device */
    ...
    };

    其中的i2c_algorithm表示算法,用于向硬件设备传输数据,其定义如下

    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 *);
    };
    1
    2
    3
    4
    5
    6
    7
    8
    总结一下:I2C驱动的主要对象是I2C总线、I2C设备、I2C驱动、I2C适配器

    I2C总线用于管理I2C设备和I2C驱动

    I2C设备描述了I2C设备的硬件信息

    I2C驱动是I2C设备对应的驱动程序

    I2C适配器是SOC上的I2C控制器,其定义了算法,可以向I2C硬件设备传输数据

    其中直接面向编写I2C设备驱动的开发者的是I2C设备和I2C驱动,I2C总线和I2C适配器是幕后工作者

    二、Linux I2C驱动框架
    I2C驱动框架可以分为四部分,I2C核心、I2C设备、I2C驱动、I2C适配器,其中I2C总线位于I2C核心中

    可以用下图来总结

    I2C核心维护着一条I2C总线,提供了注册I2C设备、I2C驱动、I2C适配器的接口

    I2C总线维护着一条设备链表和驱动链表,当向I2C核心层注册设备时,会将其添加到总线的设备链表中,然后遍历总线上的驱动链表,查看二者是否匹配,如果匹配就调用驱动的probe函数

    当注册I2C驱动时,也会将其添加到I2C总线的驱动链表中,然后遍历总线的设备链表,查看二者是否匹配,如果匹配就调用驱动的probe函数

    在I2C驱动程序中,通过I2C适配器中的算法向I2C硬件设备传输数据

    三、I2C驱动框架源码剖析
    3.1 注册I2C设备
    注册I2C适配可以通过i2c_new_device,此函数会生成一个i2c_client,指定对应的总线为I2C总线,然后向总线注册设备

    struct i2c_client *
    i2c_new_device(struct i2c_adapter *adap, struct i2c_board_info const *info)
    {
    struct i2c_client *client;
    client = kzalloc(sizeof *client, GFP_KERNEL);

    client->dev.bus = &i2c_bus_type; //指定I2C总线

    device_register(&client->dev); //向总线注册设备
    }

    看一下其中的i2c_bus_type对象,其表示I2C总线,定义了设备和驱动的匹配规则还有匹配成功后的行为

    struct bus_type i2c_bus_type = {
    .name = "i2c",
    .match = i2c_device_match, //匹配规则
    .probe = i2c_device_probe, //匹配成功后的行为
    .remove = i2c_device_remove,
    .shutdown = i2c_device_shutdown,
    .pm = &i2c_device_pm_ops,
    };

    下面再来看看device_register向总线注册设备过程中会发生什么

    device_register首先会将设备添加到总线的设备链表中,然后遍历总线的驱动链表,判断设备和驱动是否匹配,如果匹配就调用驱动的probe函数,下面看一看源码分析

    int device_register(struct device *dev)
    {
    device_add(dev);
    }

    int device_add(struct device *dev)
    {
    bus_add_device(dev); //添加设备到总线的设备链表中

    bus_probe_device(dev); //遍历总线的驱动
    }

    其中bus_add_device函数会将设备添加到总线的设备链表中,如下

    int bus_add_device(struct device *dev)
    {
    klist_add_tail(&dev->p->knode_bus, &bus->p->klist_devices);
    }
    1
    2
    3
    4
    bus_probe_device函数会遍历总线的驱动链表,如下

    void bus_probe_device(struct device *dev)
    {
    device_attach(dev);
    }
    1

    int device_attach(struct device *dev)
    {
    /* 遍历总线的驱动链表 */
    bus_for_each_drv(dev->bus, NULL, dev, __device_attach);
    }

    bus_for_each_drv(dev->bus, NULL, dev, __device_attach);会遍历总线的驱动链表的每一项,然后调用__device_attach

    static int __device_attach(struct device_driver *drv, void *data)
    {
    if (!driver_match_device(drv, dev))
    return 0;

    return driver_probe_device(drv, dev);
    }

    driver_match_device函数会判断设备和驱动是否匹配,如果匹配就调用driver_probe_device

    首先来看一看driver_match_device函数的定义

    static inline int driver_match_device(struct device_driver *drv,
    struct device *dev)
    {
    return drv->bus->match ? drv->bus->match(dev, drv) : 1;
    }

    发现它调用了总线的match函数,这里的总线在注册I2C设备的时候已经被设置为I2C总线了,定义如下

    struct bus_type i2c_bus_type = {
    .name = "i2c",
    .match = i2c_device_match, //匹配规则
    .probe = i2c_device_probe, //匹配后的行为
    .remove = i2c_device_remove,
    .shutdown = i2c_device_shutdown,
    .pm = &i2c_device_pm_ops,
    };

    所以这里会调用到i2c_device_match函数,i2c_device_match会通过I2C驱动的id_table中每一的name和I2C设备的name进行匹配

    static 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++;
    }
    return NULL;
    }

    static int i2c_device_match(struct device *dev, struct device_driver *drv)
    {
    i2c_match_id(driver->id_table, client);
    }

    如果匹配成功会调用driver_probe_device,下面再来看看driver_probe_device

    driver_probe_device函数最终会先调用到I2C总线的probe函数,然后再调用I2C驱动的probe函数

    int driver_probe_device(struct device_driver *drv, struct device *dev)
    {
    really_probe(dev, drv);
    }

    static int really_probe(struct device *dev, struct device_driver *drv)
    {

    }


    总线的probe函数为i2c_device_probe,此函数会调用驱动的probe函数

    static int i2c_device_probe(struct device *dev)
    {
    /* 调用驱动的probe函数 */
    driver->probe(client, i2c_match_id(driver->id_table, client));
    }

    3.2 注册I2C驱动
    可以通过i2c_add_driver注册I2C驱动,该函数会指定驱动对应的总线为I2C总线,然后向总线注册驱动,

    int i2c_register_driver(struct module *owner, struct i2c_driver *driver)
    {
    driver->driver.bus = &i2c_bus_type; //指定I2C总线

    driver_register(&driver->driver); //向总线注册驱动
    }

    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,
    .pm = &i2c_device_pm_ops,
    };

    driver_register函数遍历总线的设备链表进行操作,然后将驱动添加到总线的驱动链表中

    int driver_register(struct device_driver *drv)
    {
    bus_add_driver(drv);
    }

    int bus_add_driver(struct device_driver *drv)
    {
    driver_attach(drv); //此函数会遍历总线设备链表进行操作

    klist_add_tail(&priv->knode_bus, &bus->p->klist_drivers); // 添加进bus的driver链表中
    }

    int driver_attach(struct device_driver *drv)
    {
    /* 遍历总线的设备链表,调用__driver_attach */
    bus_for_each_dev(drv->bus, NULL, drv, __driver_attach);
    }

    下面来看一看__driver_attach函数,此函数会判断设备和驱动是否匹配,如果匹配就调用驱动的probe函数

    static int __driver_attach(struct device *dev, void *data)
    {
    if (!driver_match_device(drv, dev))
    return 0;

    driver_probe_device(drv, dev);
    }

    这个过程和注册设备的过程是一样的,这里不再分析

    3.3 I2C适配器的构建
    对于I2C适配器,我们不讨论它的注册细节,我们看它是如何构建的

    对于三星平台,在driversi2cussesi2c-s3c2410.c文件中构建并注册了I2C适配器,这是三星平台I2C控制器的驱动

    static const struct i2c_algorithm s3c24xx_i2c_algorithm = {
    .master_xfer = s3c24xx_i2c_xfer,
    .functionality = s3c24xx_i2c_func,
    };

    static int s3c24xx_i2c_probe(struct platform_device *pdev)
    {
    i2c->adap.algo = &s3c24xx_i2c_algorithm; //构建了算法

    i2c_add_numbered_adapter(&i2c->adap); //注册了适配器
    }

    其中的s3c24xx_i2c_algorithm中的s3c24xx_i2c_xfer就是通过操作寄存器来通过I2C控制器传输数据

    3.4 I2C数据传输
    上面介绍I2C数据传输是通过I2C适配器完成的,下面来分析一下源码

    在I2C驱动中,使用i2c_transfer来传输I2C数据,此函数肯定是通过I2C适配器的算法进行操作的,如下

    int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)
    {
    adap->algo->master_xfer(adap, msgs, num); //调用适配器的算法
    }

    ————————————————
    版权声明:本文为CSDN博主「程序员JT」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
    原文链接:https://blog.csdn.net/weixin_42462202/article/details/100083025

    Always Believe Something Beauitful Will Be Happen
  • 相关阅读:
    非网管交换机和网管交换机的区别
    百兆工业交换机与千兆工业交换机如何计算码率?
    光纤收发器的测试内容介绍
    使用expect在script中切换到root用户(精华)
    彻底解决ssh.invoke_shell() 返回的中文问题
    Python3之paramiko模块
    linux expect详解
    Apache HTTP Server 虚拟主机配置
    Apache 配置详解
    apache 基本配置
  • 原文地址:https://www.cnblogs.com/Oude/p/12441068.html
Copyright © 2011-2022 走看看