zoukankan      html  css  js  c++  java
  • linux设备驱动程序-i2c(2)-adapter和设备树的解析

    linux设备驱动程序-i2c(2)-adapter和设备树的解析

    (注: 基于beagle bone green开发板,linux4.14内核版本)

    在本系列linux内核i2c框架的前两篇,分别讲了:
    linux设备驱动程序-i2c(0)-i2c设备驱动源码实现
    linux设备驱动程序-i2c(1):i2c总线的添加与实现

    而在linux设备驱动程序--串行通信驱动框架分析中,讲到linux内核中串行通信驱动框架大体分为三层:

    • 应用层(用户空间接口操作)
    • 驱动层(包含总线、i2c-core的实现、以及总线的device和driver部分)
    • i2c硬件读写层

    在上一章节我们讲了整个总线的实现以及device和driver的匹配机制,这一章节我们要来讲讲i2c硬件读写层的实现。

    i2c的适配器

    我们来回顾一下,在本系列文章的第一章linux设备驱动程序-i2c(0)-i2c设备驱动源码实现源码中是怎么使用i2c和设备进行通信的呢?
    1、首先,在总线的device部分,使用

    struct i2c_adapter *adap = i2c_get_adapter(2)
    

    这个接口,获取一个struct i2c_adapter结构体指针,并关联到i2c_client中。

    2、然后,在总线driver的probe部分,在/dev目录下创建文件,并关联对应的file_operations结构体。

    3、在file_operations结构体的write函数中,使用

    s32 i2c_smbus_write_byte_data(const struct i2c_client *client,u8 command,u8 value);
    

    这个接口,直接向i2c设备中写数据(command和value)。

    4、 而第三点中i2c_client就是device源码部分注册到bus中的i2c_client,且包含对应的adapter,同时包含i2c地址,设备名等信息。

    如果再往深挖一层,会发现i2c_smbus_write_byte_data()的源码实现是这样的:

    s32 i2c_smbus_write_byte_data(const struct i2c_client *client, u8 command,
    		      u8 value)
    {
        union i2c_smbus_data data;
        data.byte = value;
        return i2c_smbus_xfer(client->adapter, client->addr, client->flags,
                    I2C_SMBUS_WRITE, command,
                    I2C_SMBUS_BYTE_DATA, &data);
    }
    EXPORT_SYMBOL(i2c_smbus_write_byte_data);
    

    可以看到,在i2c smbus中主导通信的就是这个adapter。

    那么,这个i2c_adapter到底是什么东西呢?

    事实上,一个硬件i2c控制器由i2c_adapter描述。

    硬件i2c控制器

    硬件i2c控制器是一个可编程器件,用于生成i2c时序,实现数据收发,且维护收发buf,对外提供寄存器接口。

    硬件控制器这一类外设一般直接挂在CPU总线上,CPU可直接寻址访问。

    当主机需要通过i2c接口收发数据时,直接通过读写硬件i2c控制器寄存器即可,硬件控制器会将主机传送过来的数据自动完成发送,接收到的数据直接放在buf中供主机读取。

    i2c_adapter的使用方式

    (注:在源码示例中,博主使用的i2c smbus的方式收发数据,为了讲解与理解的方便,这里i2c收发数据方式使用i2c_transfer接口,数据传输原理是一样的)。

    linux设备驱动程序-i2c(0)-i2c设备驱动源码实现源码中,用户只需要在驱动的device部分调用:

    struct i2c_adapter *adap = i2c_get_adapter(2)
    

    获取一个i2c硬件控制器的描述结构体,然后在通信时以这个结构体为参数即可。

    而i2c_get_adapter()接口的参数为硬件i2c控制器的num,通常,一个单板上不止一个i2c控制器,这个num指定了i2c控制器的序号。

    在驱动程序源码实现中,并不需要i2c_adapter的相关实现,那么,可以确定的是,i2c底层数据收发已经集成到了系统中,只需要用户去选择使用哪一个adapter即可。

    那么,它到底是怎么工作的呢?

    办法很简单,继续跟踪源码即可,先看一下i2c数据发送函数:

    数据的收发都基于同一个操作:先填充一个i2c_msg结构体,然后再使用i2c_tranfer函数发送数据。

    struct i2c_msg xfer[2];
    xfer[0].addr = i2c->addr;
    xfer[0].flags = 0;
    xfer[0].len = reg_size;
    xfer[0].buf = (void *)reg;
    
    xfer[1].addr = i2c->addr;
    xfer[1].flags = I2C_M_NOSTART;
    xfer[1].len = val_size;
    xfer[1].buf = (void *)val;
    i2c_transfer(i2c->adapter, xfer, 2);
    

    然后跟踪i2c_transfer()的实现:

    int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)
    {
        ...
        ret = __i2c_transfer(adap, msgs, num);
        ...
    }
    int __i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)
    {
        ...
        ret = adap->algo->master_xfer(adap, msgs, num);
        ...
    }
    

    可以清楚地从源码中看到,事实上,真正的发送数据的函数是这一个:adapter->algo->master_xfer(),那么这个adapter->algo->master_xfer函数指针是怎么被初始化的呢?

    要了解这个,我们必须先了解一个硬件i2c控制器对应的i2c_adapter是怎么被添加到系统中的。

    从设备树开始

    (linux内核版本:4.14,基于beagle bone开发板) 首先,系统在开始启动时,bootloader将设备树在内存中的开始地址传递给内核,内核开始对设备树进行解析,将设备树中的子节点(不包括子节点的子节点)转换成struct device_node节点,再由struct device_node节点转换成struct platform_device节点,如果此时在系统中存在对应的struct platform_driver节点,则调用driver驱动程序中的probe函数,在probe函数中进行一系列的初始化。

    struct i2c_adapter的注册

    正如前文所说,每一个struct i2c_adapter描述一个硬件i2c控制器,其中包含了对应的硬件i2c控制器的数据收发,同时,每一个struct i2c_adapter都直接集成在系统中,而不需要驱动开发者去实现(除非做芯片的驱动移植),那么,这个i2c adapter是怎样被注册到系统中的呢?

    在beagle bone green这块开发板中,有三个i2c控制器:i2c0~i2c2,我们以i2c0为例,查看系统的设备树文件,可以找到对i2c0的描述:

    __symbols__ {
        i2c0 = "/ocp/i2c@44e0b000";
    }
    ...
    i2c@44e0b000 {
    		compatible = "ti,omap4-i2c";
            ...
            baseboard_eeprom@50 {
    			compatible = "atmel,24c256";
    			reg = <0x50>;
    			#address-cells = <0x1>;
    			#size-cells = <0x1>;
    			phandle = <0x282>;
    			baseboard_data@0 {
    				reg = <0x0 0x100>;
    				phandle = <0x23c>;
    			};
    		};
    }
    ...
    

    可以看到,i2c0对应的compatible为"ti,omap4-i2c",如果你有了解过linux总线的匹配规则,就知道总线在对driver和device进行匹配时依据compatible字段进行匹配(当然会有其他匹配方式,但是设备树主要使用这一种方式)。

    依据这个规则,在整个linux源代码中搜索"ti,omap4-i2c"这个字段就可以找到i2c0对应的driver文件实现了。

    在i2c-omap.c(不同平台可能文件名不一样,但是按照上面从设备树开始找的方法可以找到对应的源文件)中找到了这个compatible的定义:

    static const struct of_device_id omap_i2c_of_match[] = {
        {
            .compatible = "ti,omap4-i2c",
            .data = &omap4_pdata,
        },
        ...
    }
    

    同时,根据platform driver驱动的规则,需要填充一个struct platform_driver结构体,然后注册到platform总线中,这样才能完成platfrom bus的匹配,因此,我们也可以在同文件下找到相应的初始化部分:

    static struct platform_driver omap_i2c_driver = {
        .probe		= omap_i2c_probe,
        .remove		= omap_i2c_remove,
        .driver		= {
            .name	= "omap_i2c",
            .pm	= OMAP_I2C_PM_OPS,
            .of_match_table = of_match_ptr(omap_i2c_of_match),
        },
    };
    
    static int __init omap_i2c_init_driver(void)
    {
        return platform_driver_register(&omap_i2c_driver);
    }
    

    既然platform总线的driver和device匹配上,就会调用相应的probe函数,根据.probe = omap_i2c_probe,我们再查看omap_i2c_probe函数:

    static int omap_i2c_probe(struct platform_device *pdev)
    {
        ...    //get resource from dtb node
        ...    //config i2c0 via set corresponding regs
        i2c_add_numbered_adapter(adap);
        ...    //deinit part
    }
    

    在probe函数中我们找到一个i2c_add_numbered_adapter()函数,再跟踪代码到i2c_add_numbered_adapter():

    int i2c_add_numbered_adapter(struct i2c_adapter *adap)
    {
        ...  //assert part
        return __i2c_add_numbered_adapter(adap);
    }
    

    根据名称可以隐约猜到了,这个函数的作用是添加一个i2c adapter到系统中,接着看:

    static int __i2c_add_numbered_adapter(struct i2c_adapter *adap)
    {
        ...
        return i2c_register_adapter(adap);
    }
    

    看到这里,整个i2c adapter的注册就已经清晰了,首先在设备树中会有对应的硬件i2c控制器子节点,在系统启动时,系统将设备节点转换成struct platform_device节点。

    然后系统中注册好的struct platform_driver相匹配,调用struct platform_driver驱动部分的probe函数,完成一系列的初始化和设置,生成一个i2c adapter,注册到系统中。

    adapter->algo->master_xfer的初始化

    整个流程adapter的添加流程已经梳理完成,回到我们之前的问题:

    用于实际通信中的adapter->algo->master_xfer函数指针是怎么被初始化的?
    

    答案就在i2c适配器对应的platform driver驱动部分,i2c-omap.c文件中:

    在platform driver对应的probe函数中:

    static int omap_i2c_probe(struct platform_device *pdev)
    {
        struct i2c_adapter	*adap;
        ...
        adap->algo = &omap_i2c_algo;
        r = i2c_add_numbered_adapter(adap);
        ...
    }
    

    在这个函数中对adapter的algo元素进行赋值,接着看omap_i2c_algo:

    static const struct i2c_algorithm omap_i2c_algo = {
        .master_xfer	= omap_i2c_xfer,
        .functionality	= omap_i2c_func,
    };
    

    找到了相应的.master_xfer成员,基本可以确定omap_i2c_xfer就是主机真正控制i2c收发数据的函数,adapter->algo->master_xfer指针就是指向这个函数:

    static int omap_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg msgs[], int num)
    {
        ...
        omap_i2c_xfer_msg(adap, &msgs[i], (i == (num - 1)));
        ...
    }
    

    继续跟踪omap_i2c_xfer_msg函数:

    static int omap_i2c_xfer_msg(struct i2c_adapter *adap,struct i2c_msg *msg, int stop)
    {
        ...
        omap_i2c_write_reg(omap, OMAP_I2C_CNT_REG,omap->buf_len);
        ...
        omap_i2c_write_reg(omap, OMAP_I2C_BUF_REG, w);
        ...
    }
    

    从部分成员可以看出,adapter->algo->master_xfer指针指向函数的实现就是操作i2c硬件控制器实现i2c的读写,这一部分不再细究,对应芯片手册的部分。

    到这里,adapter的初始化与注册到系统的流程就完成了。

    好了,关于linux i2c总线的adapter注册的讨论就到此为止啦,如果朋友们对于这个有什么疑问或者发现有文章中有什么错误,欢迎留言

    原创博客,转载请注明出处!

    祝各位早日实现项目丛中过,bug不沾身.

  • 相关阅读:
    轻松完成mysql4.1与mysql4.0数据库转换备份
    MYSQL数据迁徙tips,ORA00907: missing right parenthesis
    viking病毒再次感染公司LAN
    [恢]hdu 2032
    [恢]hdu 2042
    [恢]hdu 2033
    [恢]hdu 2039
    [恢]hdu 2029
    [恢]hdu 2010
    [恢]hdu 2016
  • 原文地址:https://www.cnblogs.com/downey-blog/p/10516969.html
Copyright © 2011-2022 走看看