zoukankan      html  css  js  c++  java
  • Linux I2C驱动框架

    一、I2C总线概述

    I2C是由Philips公司开发的一种简单的、双向同步串行总线,它只需要两条线即可在连接于总线上的器件之间传送信息,其硬件连接框图如下所示:

    SCL:串行时钟线,数据传输过程中用于同步的时钟信号,低电平时允许SDA线上数据改变。

    SDA:串行数据线,在时钟信号作用下,数据按位在数据线上进行传输。

    I2C总线上的设备之间通信都要遵从I2C总线协议,I2C总线由起始信号、停止信号、应答信号、非应答信号组成。

    • 起始信号:当时钟线SCL为高期间,数据线SDA由高到低的跳变。
    • 停止信号:当时钟线SCL为高期间,数据线SDA由低到高的跳变。
    • 应答信号(ACK):应答位为低电平时,规定为有效应答位,表示接收器已经成功接收到该字节。
    • 非应答信号(NACK):应答位为高电平时,规定为非应答位,一般表示接收器接收该字节没有成功。

    挂接在同一条I2C总线上的设备都要自己的物理地址,I2C主机控制器在和设备通信前需要先发送设备的地址,设备接收到总线上传过来的地址,看是否是自己的地址,如果是产生后续的应答。

    主机控制器和设备通信一般是由一个起始信号开始和一个停止信号结束,地址信息一般是7bit,发送地址的最后一位代表数据传输的方向,1表示是读,0表示写操作,其发送时序一般如下所示:

    主机发送数据

    主机读取数据


    前面对I2C总线的一些基本概念和I2C协议的做了简单的介绍,下面开始来分析Linux内核的I2C驱动框架,看看内核中如何实现对I2C设备的支持。

    二、Linux内核I2C驱动

    1、 几个重要对象

    内核中的I2C驱动框架使用了总线设备驱动模型,在分析内核I2C驱动之前,先讨论这几个重要的数据结构。

    1.1、I2C总线

    I2C总线是一条虚拟的bus总线(同platform总线一样,位于/sys/bus目录),其在driversi2ci2c-core.c实现,具体内容如下:

    struct bus_type i2c_bus_type = {
        .name        = "i2c",
        .dev_attrs    = i2c_dev_attrs,
        .match        = i2c_device_match,
        .uevent        = i2c_device_uevent,
        .probe        = i2c_device_probe,
        .remove        = i2c_device_remove,
        .shutdown    = i2c_device_shutdown,
        .suspend    = i2c_device_suspend,
        .resume        = i2c_device_resume,
    };

    这个i2c总线结构管理着I2C设备与I2C驱动的匹配、删除等操作。当有新的I2C驱动和设备加入时,会自动调用总线.match指针指向的函数i2c_device_match进行I2C设备和驱动匹配,如果匹配成功调用总线的.probe指针指向的函数i2c_device_probe

    1.2、I2C驱动

    struct i2c_driver {
        int id;
        unsigned int class;
        int (*attach_adapter)(struct i2c_adapter *); /* 设备探测函数 */
        int (*detach_adapter)(struct i2c_adapter *);
        int (*detach_client)(struct i2c_client *);
        int (*probe)(struct i2c_client *);  /* probe函数 */
        int (*remove)(struct i2c_client *); 
        void (*shutdown)(struct i2c_client *);
        int (*suspend)(struct i2c_client *, pm_message_t mesg);
        int (*resume)(struct i2c_client *);
        int (*command)(struct i2c_client *client,unsigned int cmd, void *arg);
        struct device_driver driver; /* 表示这是一个驱动 */
        struct list_head list;
    };

    驱动对应的核心数据结构,对应一个I2C驱动对象

    1.3、I2C设备

    struct i2c_client {
        unsigned short flags;        
        unsigned short addr;        /* 芯片设备地址,7bit的格式*/
        char name[I2C_NAME_SIZE];
        struct i2c_adapter *adapter;    /* 指向适配器指针,设备挂接的i2c总线上主机控制器的抽象 */
        struct i2c_driver *driver;    /* 设备的驱动程序指针 */
        int usage_count;        
        struct device dev;        /* 代表这是一个设备 */
        int irq;            /* 中断号 */
        char driver_name[KOBJ_NAME_LEN]; /* 设备名称 */
        struct list_head list;
        struct completion released;
    };

    设备对应的核心数据结构,I2C设备的抽象,对应的是I2C设备

    1.4、I2C适配器

    通过前面的介绍,知道了I2C驱动和I2C设备,我们通过I2C驱动去和I2C设备进行通讯,但驱动和设备间如何进行通信呢,这就需要一个I2C适配器,内核的I2C适配器对应的就是SOC上的I2C控制器

    struct i2c_adapter {
        struct module *owner;
        unsigned int id;                 /* 设备编号 */
        unsigned int class;
        const struct i2c_algorithm *algo; /* 算法,发送时序 */
        void *algo_data;
        int (*client_register)(struct i2c_client *);
        int (*client_unregister)(struct i2c_client *);
        u8 level;             
        struct mutex bus_lock;
        struct mutex clist_lock;
        int timeout;
        int retries;
        struct device dev;        /* 表示这是一个设备 */
        int nr;
        struct list_head clients;
        struct list_head list;
        char name[48];
        struct completion dev_released;
    };

    其中i2c_algorithm是算法的意思,知道如何去发送I2C时序来访问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);
    
        int (*algo_control)(struct i2c_adapter *, unsigned int, unsigned long);
    
        u32 (*functionality) (struct i2c_adapter *);
    };

    总的可以概括为I2C驱动有4个重要的数据结构,分别用于描述I2C总线I2C驱动I2C设备I2C适配器

    • I2C总线:挂接I2C驱动和设备,管理I2C驱动和设备的匹配和删除操作
    • I2C驱动:对应的是I2C设备的驱动程序
    • I2C设备:具体的硬件设备的抽象,抽象I2C总线上连接的I2C物理设备
    • I2C适配器:I2C驱动访问I2C设备时使用的,具体对应的是SOC上的I2C控制器的抽象

    上面讲解了内核I2C中的几个重要对象,对I2C驱动的几个重要数据结构有了基本的了解,下面通过分析内核源码来深入学习I2C驱动框架。

    2、内核源码分析

    2.1、注册I2C驱动

    编写I2C驱动程序时,通过调用i2c_add_driver函数来注册驱动程序,下面来分析这个函数,看看注册驱动程序时发生了什么。

    static inline int i2c_add_driver(struct i2c_driver *driver)
    {
        return i2c_register_driver(THIS_MODULE, driver);
    }
    int i2c_register_driver(struct module *owner, struct i2c_driver *driver)
    {
        ...
      if (is_newstyle_driver(driver)) { //判断是否是新类型的驱动方式
            if (driver->attach_adapter || driver->detach_adapter
                    || driver->detach_client) {
                 printk(KERN_WARNING
                        "i2c-core: driver [%s] is confused ",
                        driver->driver.name);
                 return -EINVAL;
            }
        }
    driver
    ->driver.bus = &i2c_bus_type; //绑定i2c_bus_type res = driver_register(&driver->driver); //向总线注册驱动
       if (res)
           return res; list_add_tail(
    &driver->list,&drivers); if (driver->attach_adapter) { //遍历内核中所有的adapter,使用driver中的attach_adapter去寻找支持的adapter struct i2c_adapter *adapter; list_for_each_entry(adapter, &adapters, list) { driver->attach_adapter(adapter); } } ... }

    由上面的代码可以看出,在调用i2c_add_driver后做了三件事情,第一,绑定了i2c_bus_type总线;第二,向总线注册了驱动;第三,遍历内核中所有注册的adapter,调用driver中的attach_adapter函数去寻找支持该设备的adapter。

    乍一看,会感觉第三件事情比较重要,匹配规则应该在这里,其实并非如此,这里仅包含了一部分的匹配规则,还有一种匹配规则在driver_register函数中。内核将i2c驱动绑定到设备分为两种方式,一种是称为“新式”驱动程序遵循标准的Linux驱动程序模型,另一种是“旧版”驱动程序自己创建设备节点。这么说的原因可以从注册I2C驱动入口处调用is_newstyle_driver判断做出分析,is_newstyle_driver是个宏,其内容如下:

    #define is_newstyle_driver(d) ((d)->probe || (d)->remove)

    注册的i2c_driver中如果有proberemove函数,被认为是”新式“驱动,并且”新式"驱动不能有attach_adapterdetach_adapter等函数,“新式”驱动driver->attach_adapter为NULL,所以前面讲的第三件事情里的操作对于“新式”驱动不会执行。由此看见,“新式”驱动使用driver_register函数进行匹配,“旧式”驱动使用i2c_driver中的attach_adapter函数去寻找支持该设备的adapter进行匹配。

    下面来分析这两种匹配方式,先来看driver_register函数。

    driver_register函数是内核总线设备驱动模型中驱动部分的注册函数,内容如下:

    int driver_register(struct device_driver * drv)
    {
        ...
        return bus_add_driver(drv);
    }
    int bus_add_driver(struct device_driver *drv)
    {
        ...
        if (drv->bus->drivers_autoprobe) {
            error = driver_attach(drv); 
            if (error)
                goto out_unregister;
        }
        /* 将驱动添加到总线的驱动链(bus->p->klist_drivers) */
        klist_add_tail(&drv->knode_bus, &bus->klist_drivers);
        ...
    }

    driver_attach函数是将注册的driverbus上的设备进行匹配的,这个函数是重头,接下来详细分析这个函数

    int driver_attach(struct device_driver * drv)
    {
        return bus_for_each_dev(drv->bus, NULL, drv, __driver_attach);
    }

    driver_attach函数调用bus_for_each_dev,来看看这个函数做了什么

    int bus_for_each_dev(struct bus_type * bus, struct device * start,
                 void * data, int (*fn)(struct device *, void *))
    {
        ...
        /* 遍历bus上挂载的所有设备(bus->p->klist_devices),执行fn函数 */
        while ((dev = next_device(&i)) && !error)
            error = fn(dev, data);
        ...
    }

    执行fn函数__driver_attach函数,也就是说driver_attach函数会遍历总线上的所有设备执行__driver_attach函数,接下来分析__driver_attach函数

    static int __driver_attach(struct device * dev, void * data)
    {
        ...
    /* 如果dev没有匹配驱动程序,调用driver_probe_device函数 */
    if (!dev->driver) driver_probe_device(drv, dev); ... }

    继续看driver_probe_device函数,通过这个函数我们可以知道匹配规则

    int driver_probe_device(struct device_driver * drv, struct device * dev)
    {
        ...
        if (drv->bus->match && !drv->bus->match(dev, drv))
            goto done;
        ...
        ret = really_probe(dev, drv);
        ...
    }

    判断注册的i2c_driver驱动挂载的总线上是否有match函数,如果存在就调用总线上的match函数进行设备和驱动的匹配。在前面我们已经列出了i2c_driver挂载的i2c总线,match函数指针指向i2c_device_match函数

    static int i2c_device_match(struct device *dev, struct device_driver *drv)
    {
      struct i2c_client    *client = to_i2c_client(dev); //使用container_of获取i2c_client
        struct i2c_driver    *driver = to_i2c_driver(drv); //使用container_of获取i2c_driver ...
    if (!is_newstyle_driver(driver)) return 0; ... return strcmp(client->driver_name, drv->name) == 0; }

    驱动和设备的匹配很简单,通过比较名称,如果驱动名称和设备名称一致则代表匹配成功。

    好了,现在弄清楚匹配规则了,下面来看一看如果匹配成功后要干嘛,也就是要分析really_probe函数

    static int really_probe(struct device *dev, struct device_driver *drv)
    {
        ...
        dev->driver = drv;    /* 让dev的driver指向当前匹配的drv,和前面相互呼应,对于一个设备只能匹配一个驱动程序 */
        ...
        if (dev->bus->probe) {
            ret = dev->bus->probe(dev);
            if (ret)
                goto probe_failed;
        } else if (drv->probe) {
            ret = drv->probe(dev);
            if (ret)
                goto probe_failed;
        }
        ...
    }

    really_probe函数判断设备挂载的总线上的probe函数是否存在,如果存在调用总线上的probe函数,显然dev肯定也是挂载到前面我们已经列出了i2c_driver挂载的i2c总线probe函数指针指向i2c_device_probeprobe函数存在

    static int i2c_device_probe(struct device *dev)
    {
        struct i2c_client    *client = to_i2c_client(dev);
        struct i2c_driver    *driver = to_i2c_driver(dev->driver);
        ...
        return driver->probe(client);
    }

    至此,“新式”驱动i2c_driver注册分析完成,当向内核注册i2c驱动时,会将i2c驱动添加到总线的链表中,遍历总线上所有设备,通过i2c_client->driver_name i2c_driver->name进行字符串匹配,如果匹配成功,就调用驱动程序的probe函数。

    下面来分析另一种匹配方式,”旧式“驱动程序匹配,继续看代码

    int i2c_register_driver(struct module *owner, struct i2c_driver *driver)
    {
        ...
        /* legacy drivers scan i2c busses directly */
        if (driver->attach_adapter) {
            struct i2c_adapter *adapter;
    
            list_for_each_entry(adapter, &adapters, list) {
                driver->attach_adapter(adapter);
            }
        }
        ...
    }

    list_for_each_entry:找到内核中注册的所有adapter,内核注册的adapter挂接在adapters为头部链表中,查找内核中注册的所有adapter即遍历adapters为头部链表中的所有节点。

    调用注册driver中的attach_adapter函数,那么attach_adapter函数内容时什么呢?搜索内核找到内核自带的i2c设备驱动,以eeprom驱动为例,发现其实现的attach_adapter即调用i2c_probe函数

    int i2c_probe(struct i2c_adapter *adapter,
              struct i2c_client_address_data *address_data,
              int (*found_proc) (struct i2c_adapter *, int, int))
    {
        ...
            err = i2c_probe_address(adapter, address_data->normal_i2c[i],
                        -1, found_proc);
        ...
    }
    static int i2c_probe_address(struct i2c_adapter *adapter, int addr, int kind,
                     int (*found_proc) (struct i2c_adapter *, int, int))
    {
        ...
    
        if (i2c_check_addr(adapter, addr))
            return 0;
    
        ...
            if (i2c_smbus_xfer(adapter, addr, 0, 0, 0,
                       I2C_SMBUS_QUICK, NULL) < 0)
                return 0;
    
            /* prevent 24RF08 corruption */
            if ((addr & ~0x0f) == 0x50)
                i2c_smbus_xfer(adapter, addr, 0, 0, 0,
                           I2C_SMBUS_QUICK, NULL);
        }
    
        err = found_proc(adapter, addr, kind);
        ...
    }

    i2c_probe函数做了三件事情,第一,调用i2c_check_addr,该函数判断传入的i2c设备的地址是否合法;第二,调用i2c_smbus_xfer,该函数即调用adapter->algo->smbus_xfer函数,该函数使用SOC上I2C控制器向I2C总线发送特定设备地址,看设备是否有应答;第三,如果第二步检测但设备,调用found_proc函数,found_proc函数是驱动编写者实现的,一般即创建并初始化i2c_client,调用i2c_attach_client向内核注册,注册字符设备驱动等。

    为什么会有“旧式”这种方式?如果在不知道i2c设备在哪一条总线的情况下,这种方式就发挥了作用

    上面分析了注册I2C驱动,下面来分析注册I2C设备

    2.2、注册I2C设备

    内核通过i2c_new_device注册i2c设备

    struct i2c_client *
    i2c_new_device(struct i2c_adapter *adap, struct i2c_board_info const *info)

    注册设备时需要指定i2c适配器和设备信息

    一台机器上可能有多个i2c控制器,不同的i2c控制器在linux内核中被抽象成适配器来表示,适配器中包含了它所其连接的i2c总线的访问方法。所有在注册设备时,设备接在了哪条i2c总线上,就设置哪一条总线的适配器。

    i2c_adapter可以通过i2c_get_adapter来获取

    struct i2c_adapter* i2c_get_adapter(int id)

    i2c_board_info结构体描述了设备的硬件信息

    struct i2c_board_info {
        char        driver_name[KOBJ_NAME_LEN]; //设备名称,用于与驱动匹配
        char        type[I2C_NAME_SIZE];
        unsigned short    flags;
        unsigned short    addr;                 //设备地址
        void        *platform_data;
        int        irq;                         //中断号
    };

    下面来详细分析i2c_new_device函数

    struct i2c_client *
    i2c_new_device(struct i2c_adapter *adap, struct i2c_board_info const *info)
    {
        struct i2c_client    *client;
        int            status;
    
        client = kzalloc(sizeof *client, GFP_KERNEL);        //分配一个i2c_client设备
        if (!client)
            return NULL;
    
        client->adapter = adap;                              //设置i2c_client成员
    
        client->dev.platform_data = info->platform_data;
        client->flags = info->flags;
        client->addr = info->addr;
        client->irq = info->irq;
    
        strlcpy(client->driver_name, info->driver_name,
            sizeof(client->driver_name));
        strlcpy(client->name, info->type, sizeof(client->name));
    
        status = i2c_attach_client(client);                 //调用i2c_attach_client
        if (status < 0) {
            kfree(client);
            client = NULL;
        }
        return client;
    }

    i2c_new_device函数中分配了一个i2c_client设备,并根据传入的适配器和设备信息初始化i2c_client,最后调用i2c_attach_client函数

    要想弄清楚i2c_new_device函数,还需要继续在分析i2c_attach_client函数

    int i2c_attach_client(struct i2c_client *client)
    {
        struct i2c_adapter *adapter = client->adapter;
        ...
        list_add_tail(&client->list,&adapter->clients);
        ...
        client->dev.bus = &i2c_bus_type;
        ...
        res = device_register(&client->dev);
        ...
    }

    i2c_attach_client函数将注册的i2c_client挂载到i2c_bus_type这条i2c总线上,并调用了device_register函数

    这个device_register和前面介绍的i2c_driver注册时调用的driver_register的功能相似。注册一个设备并查找bus总线下所有挂载的driver,调用bus的match函数进行匹配,如果匹配成功调用i2c_driver中的probe函数

    至于匹配的规则我们在前面介绍i2c_driver时已经分析过了,这里就不做详细的介绍。

    2.3、注册I2C适配器

    每注册一个I2C适配器代表机器的上一个物理的I2C控制器,注册的I2C适配器中包含了I2C控制器发送I2C协议访问I2C总线的方法,内核中使用i2c_add_adapter函数来注册一个i2c适配器

    int i2c_add_adapter(struct i2c_adapter *adapter)
    {
            ...
        return i2c_register_adapter(adapter);
    }
    static int i2c_register_adapter(struct i2c_adapter *adap)
    {
        ...
        list_add_tail(&adap->list, &adapters);
        ...
        ...
        adap->dev.class = &i2c_adapter_class;
        res = device_register(&adap->dev);
        ...
        list_for_each(item,&drivers) {
            driver = list_entry(item, struct i2c_driver, list);
            if (driver->attach_adapter)
    
                driver->attach_adapter(adap);
        }
    }

    将注册的i2c_adapter添加到adapters链表的尾部,调用device_register注册一个i2c_adapter设备,注册的device将存在虚拟文件系统的/sys/bus/platform/devices目录。

    最核心的是函数的最后一部分内容,遍历内核中注册的i2c_driver,调用i2c_driver中的attach_adapter函数。

    由此可见注册一个i2c适配器时,使用“旧式”驱动去扫描该i2c控制器的挂接的总线上挂接的所有i2c设备

    三、总结

    内核的i2c框架中实现了两种驱动和设备的匹配方式,一种是基于总线——设备——驱动模型的方式,另一种是直接找到内核中注册的所有适配器,调用适配器中的算法发送i2c协议来查看i2c设备是否应答,从而创建i2c设备的方式。内核在注册驱动时去分辨这两种注册的方式,如果注册的i2c_driver中实现了probe函数,被识别成使用总线设备驱动模型的方式,这种方式需要实现一个i2c_client,i2c_client中指定设备名称和支持该设备的适配器。设备或驱动在注册时,分别调用总线中挂载的驱动或设备,调用总线的.match函数比较两者名称,名称相同驱动和设备匹配成功,如果匹配成功调用i2c_driver的probe函数。第二种方式,在不知道i2c设备在哪一条总线的情况下,这种方式就发挥了作用。这种方式调用i2c_driver中.attach_adapter函数查找到驱动支持的适配器,找到对应的适配器需要自己创建i2c_client,创建设备节点等,所有对应第二种驱动需要驱动编写者去实现i2c_driver中.attach_adapter函数

  • 相关阅读:
    自动对一个文件夹下的N个word文件批量执行一个宏
    PHP正则匹配联系方式手机号、QQ、微信、邮箱、固定电话
    私信基本功能数据库设计
    ArcGIS三分式标注、四分式标注和同时上下标实现
    Word2019文档中将页面边框更改为文本边框的方法
    Arcgis彻底删除和卸载
    ArcMap中各种基本概念的介绍
    ArcGIS Python工具箱.pyt裁剪工具
    C# Object对象的ToString方法在转换日期时丢失毫秒
    2020年糖尿病领域中国学者学术影响力排名
  • 原文地址:https://www.cnblogs.com/053179hu/p/14099462.html
Copyright © 2011-2022 走看看