基于Linux的IIC驱动框架源码分析

文章目录

    • 1、前言
    • 2、IIC 驱动框架(该节内容完全参考于[Linux架构师](https://zhuanlan.zhihu.com/p/455521103))
    • 3、IIC 框架中的数据结构
      • 3.1、i2c_adapter
        • 3.1.1、algo
        • 3.1.2、struct device dev
        • 3.1.3、timeout、retries
        • 3.1.4、nr、name
        • 3.1.5、dev_released
        • 3.1.6、userspace_clients_lock,userspace_clients
      • 3.2、i2c_board_info
      • 3.3、i2c_client
        • 3.3.1、addr,flags,name,irq
        • 3.3.2、struct device dev
        • 3.3.3、detected
      • 3.4、i2c_devinfo
      • 3.5、i2c_msg
      • 3.6、i2c_driver
        • 3.6.1、attach_adapter
        • 3.6.2、address_list、detect、clients、class
    • 4、IIC 框架初始化流程图
    • 5、IIC 框架中的关键函数
      • 5.1、i2c_init
      • 5.2、i2c_register_adapter
        • 5.2.1、device_register
        • 5.2.2、bus_for_each_drv
        • 5.2.3、of_i2c_register_devices
          • 5.2.3.1、device_register
        • 5.2.4、i2c_scan_static_board_info
      • 5.3、i2c_register_driver
        • 5.3.1、driver_register
        • 5.3.2、i2c_for_each_dev
      • 5.4、i2c_transfer
      • 5.5、i2c_dev_init
      • 5.6、i2c_master_send,i2c_master_recv
    • 6、参考资料


1、前言

​   我个人感觉学习一个驱动框架,先了解下驱动框架涉及到的数据结构体是个十分必要的东西。知道数据结构体里面每个成员的含义以及作用后,再去看源码就只跟自身C语言功力和框架中涉及到其他概念知识相关了。所以讲解IIC驱动源码之前,我不会只把里面涉及到的数据结构体成员注释贴出来就行,我还会讲里面每个成员具体的作用和为什么要定义这个成员。

​   本篇博客是以I.MX6ULL为基础的IIC驱动框架分析,由于I.MX6ULL芯片的IIC控制器不支持DMA传输,所以在分析源码时降低了一部分难度。但是实际DMA传输在Linux驱动中用的挺频繁的,比如我后面博客会写的SPI驱动框架和UART驱动框架就使用到了DMA,所以对这两部分驱动框架有兴趣的朋友可以去我主页看下相关的博客。另外,由于本人能力有限,所以只会讲解其中关键的流程和函数,如果有什么错误的地方,欢迎大家指出。

2、IIC 驱动框架(该节内容完全参考于Linux架构师)

基于Linux的IIC驱动框架源码分析_第1张图片

  IIC大的软件框架如上图所示:

  1、应用层通过标准的 open 调用进行 IIC 设备的操作;
  2、每一个 i2c_client 对应一个实际硬件上的 IIC device(比如 EEPROM)
  3、每一个 i2c_driver 描述一种 IIC 设备的驱动
  4、i2c_dev 是注册的字符类型的设备
  5、i2c_core 是 IIC 核心层,提供了总线、驱动、通信方式等的注册和钩子函数的设置
  6、i2c_adapter 用于描述一个 SoC 的一个 IIC 控制器
  7、i2c_algorithm 用于底层对接实际的控制器,产生 IIC 硬件波形的函数
  8、下面对接的就是实际的 SoC 的 IIC 控制器(寄存器)和硬件

​   IIC 的 Linux 软件结构与 Linux SPI 部分的基本一致,主要的代码实现集中在:drivers/i2c/目录下,首先我们先认识一下 Linux 是怎么抽象 IIC 的,这对于更好的理解代码,至关重要。

基于Linux的IIC驱动框架源码分析_第2张图片
​   这张图,是在描述软件对硬件的抽象过程,非红的部分,是硬件,SoC 上挂了 N 个 IIC 的控制器,每个控制器上,挂了 M 个设备。红色部分,是 Linux 针对硬件抽象出来的软件结构,由此可以看到,Linux 驱动中:

  1、i2c_adatper:描述一个实际的 IIC 物理硬件。
  2、i2c_algorithm:函数指针集,钩子函数,用于描述特定 SoC 硬件的 IIC 模块产生通信波形的方法。
  3、i2c_client:描述一个挂接到 IIC 总线上的具体物理设备。
  4、i2c_driver:用于描述一个 IIC 设备的驱动。

3、IIC 框架中的数据结构

3.1、i2c_adapter

​   i2c_adatper 用于描述一个实际的 IIC 控制器,它的定义在 include/linux/i2c.h 文件,如下:

struct i2c_adapter	adapter
{
    struct module *owner = THIS_MODULE;
    unsigned int class;		  					
    const struct i2c_algorithm *algo = i2c_imx_algo; 						// 总线通信方法结构体指针
    void *algo_data;

    struct rt_mutex bus_lock = rt_mutex_init(&adap->bus_lock);				// 控制并发访问的自旋锁

    int timeout = CONFIG_HZ;											 // 重传超时时间
    int retries;														// 重传次数
    struct device dev;													// 适配器设备
    {
        struct device *parent = &pdev->dev;								  //指i2c1这个设备
        struct device_node	*of_node = pdev->dev.of_node; 				   //指i2c1这个设备节点
        struct bus_type	*bus = &i2c_bus_type;
        struct device_type *type = &i2c_adapter_type;
        struct kobject kobj.name = "i2c-%d";							//适配器在/sys/class/iic-dev目录下的名字
    }

    int nr = pdev->id;													// I2C适配器的ID号
    char name[48];														// 适配器名称
    struct completion dev_released;										// 在注销适配器时完成同步作用

    struct mutex userspace_clients_lock = mutex_init(&adap->userspace_clients_lock);
    struct list_head userspace_clients = INIT_LIST_HEAD(&adap->userspace_clients);			// client 链表头

    struct i2c_bus_recovery_info *bus_recovery_info;
    const struct i2c_adapter_quirks *quirks;
}

以上基本每个变量我都赋了值,以方便查看每个变量的具体情况。

3.1.1、algo

​   algo是i2c_adatper中的核心成员,它包含了操作这个 IIC 控制器的函数集,也就是直接对接到实际的 SoC 的 IIC 控制器的操作(寄存器配置)。algo的核心成员如下:

struct i2c_algorithm {
	int (*master_xfer)(struct i2c_adapter *adap, struct i2c_msg *msgs, int num);
	u32 (*functionality) (struct i2c_adapter *);
};

master_xfer 为 IIC 控制器的消息传递函数,最终驱动层调用的 i2c_transfer 函数,就是将master_xfer进行一层封装。functionality 可以获取 IIC 控制器支持的功能,比如是否支持 IIC 总线或者 SMBUS。因为这两个函数为函数指针,所以可以根据不同的 Soc 的 IIC 控制器编写相应的通信方法。

3.1.2、struct device dev

​   dev用来完成 IIC 控制器的注册,该成员只列以下几个成员,如下:

struct device dev													// 适配器设备
{
    struct bus_type	*bus = &i2c_bus_type;							  
    struct device_type *type = &i2c_adapter_type;
    struct kobject kobj.name = "i2c-%d";	//适配器在 /sys/devices/platform/soc/2100000.aips-bus/21a0000.i2c/目录下的名字
}

bus 用来指明设备属于哪个总线,type 用来指明设备属于哪种类型,kobj.name 为 IIC 控制器在 /sys/devices/platform/soc/2100000.aips-bus/21a0000.i2c/目录下的名字。i2c_bus_type 表示该设备属于 IIC 总线下的设备,凡是指明了总线类型的设备都会被划分到该总线下管理,这样一旦有相匹配的设备和驱动,就将会调用 IIC 总线下的 match 函数。 i2c_adapter_type 表示该设备属于 i2c_adapter 设备类型,该结构体中包含了 i2c_adapter 中专有的属性文件,当设备注册成功后,将会在设备文件中生成相应的属性文件,这样就可以通过sysfs接口访问属性文件从而操作 IIC 设备。

​   注:最开始我搞混了,我以为该设备文件会注册到/sys/class/i2c-dev目录下,而且在该目录下的确有个 i2c-%d 设备文件,以至于我一直这么想。但是后来发现在设备注册过程中,它并有指定class成员的类,猜想到该设备其实并没有在/sys/class目录下创建。所以,又看了 drivers\i2c\i2c-dev.c 文件,发现里面的 i2c_dev_init 函数注册了i2c-dev类,也就是/sys/class/i2c-dev目录的由来,但是该文件下也创建了一个i2c-%d设备文件,这就把我搞晕了。后来我去看 /sys/class/i2c-dev/i2c-%d 设备文件中的属性文件,发现只有name这么一个属性文件,这时我才发现注册适配器设备时,节点并没有生成在/sys/class/i2c-dev/目录下,而是在 /sys/devices/platform/soc/2100000.aips-bus/21a0000.i2c/目录下,因为在该目录中才有 i2c_adapter_type 中定义的 new_device 和delete_device 属性文件。

3.1.3、timeout、retries

​   retries 为 IIC 控制器的重传次数,比如 IIC 控制器在传输过程中由于仲裁丢失,总线忙等情况而发送消息失败,则会根据retries的值来规定最大的重传次数。timeout为 IIC 控制器发送一次消息所需的最长时间,包括重传次数所消耗的总时间,如果在timeout时间内没有发送完成,则表示此次消息发送失败。

3.1.4、nr、name

​   nr为 IIC 控制器的ID号,用来区分是属于 Soc 的第几个 IIC 控制器。name为 IIC 控制器的名字。一般这两个值都是通过设备树上的 IIC 节点解析出来的。

3.1.5、dev_released

​   dev_released用来实现同步,实现的机制为complete,作用跟信号量一样,只不过该信号解决了信号量在多进程操作中的一些缺点。具体使用方法可以参考该链接:linux同步机制-complete
  该成员使用在 IIC 控制器注销的过程中。

3.1.6、userspace_clients_lock,userspace_clients

​   userspace_clients 是一个链表头,专门链接 i2c_client 类型的设备,这些设备也就是像EEPROM、TP (触摸屏)这类的 IIC 设备。在设备树中定义了很多 IIC 设备,将来都会被转换成 i2c_client 类型的结构体,但是!userspace_clients并不链接这些 IIC 设备。userspace_clients链接的是我们在应用层通过访问 IIC 控制器的属性文件创建的 IIC 设备。userspace_clients_lock就是在添加该链表过程中使用的互斥锁。

3.2、i2c_board_info

​   i2c_board_info 用于描述具体的 IIC 从设备,它的定义在 include/linux/i2c.h 文件,如下:

struct i2c_board_info 
{
	char			type[I2C_NAME_SIZE];										//从设备名	
	unsigned short	flags;
	unsigned short	addr = be32_to_cpup(addr);									//从设备地址;
	void		   *platform_data;											  //由开发人员定义
	struct dev_archdata	*archdata;												//由开发人员定义
	struct device_node *of_node = of_node_get(node);							//记录从设备的设备节点
	struct fwnode_handle *fwnode;
	int		irq;															//从设备的中断号
};

​   i2c_board_info 中的大部分成员都是通过解析设备树获得,而i2c_board_info 将来会被转换成 i2c_client 类型,i2c_client 和 i2c_board_info 内部很多成员都是相似的,i2c_client 中的很多成员变量值都继承于i2c_board_info。type 将初始化 i2c_client.name,最后会与从设备驱动中 i2c_device_id 的 name 做匹配;addr 将初始化 i2c_client.addr;flags 将初始化 i2c_client.flags;platform_data 将初始化 i2c_client.dev.platform_data;archdata 将初始化 i2c_client.dev.archdata;irq 将初始化 i2c_client.irq 。

3.3、i2c_client

​   i2c_client 表示从设备的最终形态, 包含与从设备关联的完整信息。它内部成员的值很多来自于 i2c_board_info。它的定义在 include/linux/i2c.h 文件,如下:

struct i2c_client 
{
	unsigned short flags = info->flags;		 /* 标志 */ 
	unsigned short addr = info->addr;		 /* 低 7 位为芯片地址 */
	char name[I2C_NAME_SIZE];				/* 设备名称 */ 
	struct i2c_adapter *adapter = adap;		 /* 依附的 i2c_adapter*/ 
	struct device dev;						/* 设备结构体 */ 
	{
		struct device *parent = &client->adapter->dev;		/*建立从设备与适配器的父子关系*/			
		struct bus_type	*bus = &i2c_bus_type;
		struct device_type *type = &i2c_client_type;
		struct kobject kobj.name = "%d-%04x"(1-000e);
	}
	int irq = info->irq;					/* 从设备中断号	*/
	struct list_head detected;				/* 链表头 */
};

3.3.1、addr,flags,name,irq

​   这四个成员来源于 i2c_board_info,但是 irq 这个成员很特殊。irq 成员表示从设备的中断号,而中断号也是通解析设备树中从设备节点获得,但是在构建 i2c_board_info 时,函数中并没有去获取从设备的中断号,也就是说,当 i2c_board_info 转换成 i2c_client 后,i2c_client中的irq是没有被赋值的。后来我继续翻了下源码,才发现i2c_client中的irq是在总线匹配成功后,调用i2c_bus_type的i2c_device_probe函数才解析从设备节点中的中断号。

3.3.2、struct device dev

​   dev用来完成 IIC 从设备的注册,该成员只列以下几个成员,如下:

struct device dev;						
{
    struct device *parent = &client->adapter->dev;		/*建立从设备与适配器的父子关系*/			
    struct bus_type	*bus = &i2c_bus_type;
    struct device_type *type = &i2c_client_type;
    struct kobject kobj.name = "%d-%04x"(1-000e);
}

bus成员已经在3.1.2中讲过。parent用来指定从设备的父设备,这里指定的是 IIC 控制器,这样设备注册时将会在 /sys/devices/platform/soc/2100000.aips-bus/21a0000.i2c/i2c-%d目录下生成从设备的设备文件。i2c_client_type跟3.1.2中讲到的i2c_adapter_type类似,指定了从设备的类型,该类型中也定义了从设备专有的属性文件。kobj.name就是在/ /sys/devices/platform/soc/2100000.aips-bus/21a0000.i2c/i2c-%d目录下生成的设备文件名。

3.3.3、detected

​   detected为一个链表头,3.1.6中讲到的在应用层创建的 IIC 从设备就是通过该成员链接到i2c_adapter中的userspace_clients。

3.4、i2c_devinfo

​   i2c_devinfoi2c_board_info 类似,同样用于描述具体的 IIC 从设备,它的定义在drivers\i2c\i2c-core.h文件,如下:

struct i2c_devinfo {
	struct list_head	list;
	int			busnum;
	struct i2c_board_info	board_info;
};

可以看到 i2c_devinfo 中包含了 i2c_board_info,那么这两者有什么联系呢?其实 i2c_devinfo 在如今采用设备树的内核驱动已经很少被用到,在还没有设备树时,都是开发人员自己去定义一个 i2c_devinfo ,然后通过 i2c_register_board_info 函数注册到 i2c_board_list 总链表上。最后在注册 IIC 控制器的时候,遍历 __i2c_board_list 总链表,将上面挂接的每个 i2c_devinfo 转换为 i2c_client。然而在如今设备树的内核版本,都是解析设备树生成 i2c_board_info,然后把 i2c_board_info 转换为 i2c_client。

​   i2c_board_info跟3.2中一样,busnum表示该从设备是属于哪一个 IIC 控制器,list将被挂接到 i2c_board_list 总链表上。

3.5、i2c_msg

​   i2c_msg 描述一个 IIC 数据,它的定义在 include/linux/i2c.h 文件,如下:

struct i2c_msg {
	__u16 addr;					//从设备地址			
	__u16 flags;
	__u16 len;					//发送字节长度				
	__u8 *buf;					//发送缓存区			
};

IIC 传输数据时都是以i2c_msg类型为一个数据包。这里重点说下flags标志位:

​   1、在 i2c_board_info 中传递的 flags,只起区分 addr 是 7 位还是 10 位的作用,默认为 7位地址,当使用 7 位 addr 时无需设置。
​   2、fags 首先继承上面的来源于 i2c_board_info 的值,具备区分 addr 是 7 位还是 10 位的功能 , 然后在 i2c_master_recv()、 i2c_master_send()中再进一步依据是读写操作, 来设置读写操作标记以区分读写操作。
​   3、如果驱动中不调用子系统的这两个接口(i2c_master_recv()、 i2c_master_send() ),则需相应自行设置 flags 读写位。

3.6、i2c_driver

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 *);

	void (*alert)(struct i2c_client *, unsigned int data);

	int (*command)(struct i2c_client *client, unsigned int cmd, void *arg);

	struct device_driver driver;									
	const struct i2c_device_id *id_table;							//id匹配表

	int (*detect)(struct i2c_client *, struct i2c_board_info *);
	const unsigned short *address_list;
    
	struct list_head clients;
};

​   里面的probe、remove、shutdown、driver、id_table成员大家已经很熟悉了,这里我讲下其他不常见的成员,这些成员可能用的很少,也有的已经被内核遗弃不再使用,但为了内容完整性,我讲下这些不常见的成员的作用。

3.6.1、attach_adapter

​   这个成员已经基本在如今的内核版本不再使用,该成员主要是在一些可以遍历所有注册成功的驱动的函数中使用,目的就是遍历内核中注册的所有驱动,通知驱动一些消息,比如后面会讲到的 bus_for_each_drv 函数就是遍历 IIC 总线上的所有驱动,告诉他们有新的适配器注册。至于通知过后,每个驱动干什么,就由 attach_adapter 函数自己定义。

3.6.2、address_list、detect、clients、class

​   class 表示当前从设备驱动使用的适配器的类型,它将与上面 i2c_adapter{} 的 class 进行匹配判断。address_list 指向从设备地址表,该表存放了需要创建的从设备地址,该表由开发人员自己定义 。如果是有新的适配器注册,将会遍历IIC 总线上的所有驱动,将新的i2c_adapter中的class成员与遍历到的i2c_driver的class成员匹配,如果匹配成功,将会使用新注册的适配器和address_list中的从设备地址信息注册新的i2c_client从设备。其中clients就是专门链接此过程中创建的i2c_client从设备,而detect就是在此过程调用的函数,该函数由开发人员自定义。而如果是有新的 IIC 设备驱动注册,将会遍历 IIC 总线上的所有适配器,查看有没有适配器与自身i2c_driver的class成员匹配,如果有,则与上面说的过程一样。

4、IIC 框架初始化流程图

基于Linux的IIC驱动框架源码分析_第3张图片

以上就是 IIC 驱动框架做的主要事情,其中绿色部分为无设备树内核版本的步骤,如果有设备树内核版本则没有绿色那一步。

5、IIC 框架中的关键函数

5.1、i2c_init

static int __init i2c_init(void)
{
	int retval;

	retval = of_alias_get_highest_id("i2c");			//得到Soc中IIC控制器个数 
    ......
	retval = bus_register(&i2c_bus_type);				//会创建/sys/bus/i2c/目录和子目录 drivers、 devices,及相关属性文件
#ifdef CONFIG_I2C_COMPAT
	i2c_adapter_compat_class = class_compat_register("i2c-adapter");	//会创建 sys/class/i2c-adapter/目录
	......
#endif
	retval = i2c_add_driver(&dummy_driver);								//注册dummy_driver驱动,我不清楚这个注册有什么用
	......
}

5.2、i2c_register_adapter

static int i2c_register_adapter(struct i2c_adapter *adap)
{
	int res = 0;
	......
    /* 3.1 中讲到这几个变量作用 */
	rt_mutex_init(&adap->bus_lock);							//初始化控制并发访问的自旋锁
	mutex_init(&adap->userspace_clients_lock);				 //初始化互斥锁
	INIT_LIST_HEAD(&adap->userspace_clients);				//初始化链表
	......
    /* 3.1.3 中讲到该变量作用 */
	if (adap->timeout == 0)
		adap->timeout = HZ;									//设置超时时间
	......
	dev_set_name(&adap->dev, "i2c-%d", adap->nr);			 //设置adapter名字,注册后会生成以下节点/sys/class/i2c-dev/i2c-%d
	adap->dev.bus = &i2c_bus_type;							//设置适配器总线类型
	adap->dev.type = &i2c_adapter_type;						//设置适配器设备类型
	res = device_register(&adap->dev);						//注册设备
	......

	of_i2c_register_devices(adap);							// 通过设备树节点注册所有该控制器下的所有从设备
	i2c_scan_static_board_info(adap);						// 通过mach文件注册所有该控制器下的所有从设备

	mutex_lock(&core_lock);
    //遍历 i2c 总线 i2c_bus_type 驱动链表上的每个驱动! 对每个驱动调用__process_new_adapter
	bus_for_each_drv(&i2c_bus_type, NULL, adap, __process_new_adapter);	
	mutex_unlock(&core_lock);
}
 	

5.2.1、device_register

​   device_register函数主要完成了3件事情:1、创建/sys/devices/platform/soc/2100000.aips-bus/21a0000.i2c/i2c-%d目录;2、将适配器设备挂入 i2c 总线 i2c_bus_type 的设备链表;2、遍历总线 i2c_bus_type 驱动链表上的每个驱动, 当前 i2c 总线 i2c_bus_type 的驱动链表为空,无操作返回;

5.2.2、bus_for_each_drv

​   此处 bus_for_each_drv() 遍历 i2c 总线 i2c_bus_type 驱动链表上的每个驱动,对每个驱动调用 process_new_adapter,告诉每个驱动有新的适配器注册,该函数中调用了i2c_do_add_adapter() 函数,该函数具体如下:

int i2c_do_add_adapter(struct i2c_driver *driver, struct i2c_adapter *adap)
{
    //如果驱动的 i2c_driver{}中未实现所需接口,将无操作返回。
    i2c_detect(adap, driver);
    if (driver->attach_adapter) {
   		 driver->attach_adapter(adap);
    }
}

由于该阶段还没有驱动注册,所以i2c 总线的驱动链表为空,bus_for_each_drv()也会直接返回。 i2c_detect 函数中的过程基本在3.6.2中讲到, attach_adapter 函数为也在3.6.2中说明。

5.2.3、of_i2c_register_devices

static void of_i2c_register_devices(struct i2c_adapter *adap)
{
	//遍历适配下的所有从设备节点,并将每个从设备节点用i2c_client类型表示
	for_each_available_child_of_node(adap->dev.of_node, node)			
		of_i2c_register_device(adap, node);
		{
			struct i2c_client *result;
			struct i2c_board_info info = {};
			......
			of_modalias_node(node, info.type, sizeof(info.type));		//得到从设备名(从compatible属性中提取)
             addr = of_get_property(node, "reg", &len);					//获取从设备的IIC地址 
			info.addr = be32_to_cpup(addr);							 //存储从设备地址
			info.of_node = of_node_get(node);						 //记录从设备的设备节点
			......
			if (of_get_property(node, "wakeup-source", NULL))			//取设备树节点wakeup-source信息
				info.flags |= I2C_CLIENT_WAKE;

			result = i2c_new_device(adap, &info);						//将i2c_board_info转换成i2c_client并注册到i2c总线
			{
                    struct i2c_client	*client;
                    int			status;

                    client = kzalloc(sizeof *client, GFP_KERNEL);			//给i2c_client分配内存
                    /*将adapter的地址保存到i2c_client->adapter,在驱动函数中可以通过i2c_client找到adapter*/
                    client->adapter = adap;			

                    client->flags = info->flags;							 //保存从设备地址类型
                    client->addr = info->addr;								//保存从设备地址					 
                    client->irq = info->irq;								//保存从设备中断号
                    strlcpy(client->name, info->type, sizeof(client->name));	//保存从设备名

                    status = i2c_check_client_addr_validity(client);		//检测从设备地址是否合法,主要检查位数
                    status = i2c_check_addr_busy(adap, client->addr);		//检测从设备地址是否被占用
                    
                    client->dev.parent = &client->adapter->dev;				//建立从设备与适配器的父子关系
                    client->dev.bus = &i2c_bus_type;						//设置从设备总线类型
                    client->dev.type = &i2c_client_type;					//设置从设备设备类型
                    client->dev.of_node = info->of_node;					//设置从设备节点
                    client->dev.fwnode = info->fwnode;
                    
                    /*设置从设备名,格式为"%d-%04x",如1-000e,表示在IIC 控制器1下,地址为0e。改名字将会生成节点/sys/class/iic-						dev/iic-1/1-000e */
                    i2c_dev_set_name(adap, client);							
                    status = device_register(&client->dev);					//注册到Linux核心
                    return client;
			}
			return result;
		}
}
5.2.3.1、device_register

​   device_register函数主要完成了3件事情:1、创建/sys/devices/platform/soc/2100000.aips-bus/21a0000.i2c/i2c-%d/%d-%04x/目录及该目录下的属性文件;2、将 i2c_client{}从设备挂入 i2c 总线 i2c_bus_type 的设备链表;3、遍历 i2c 总线 i2c_bus_type 驱动链表上的每个驱动, 当前 i2c 总线 i2c_bus_type 的驱动链表为空,无操作返回;

5.2.4、i2c_scan_static_board_info

static void i2c_scan_static_board_info(struct i2c_adapter *adapter)
{
	struct i2c_devinfo	*devinfo;

	down_read(&__i2c_board_lock);
	list_for_each_entry(devinfo, &__i2c_board_list, list) {
		if (devinfo->busnum == adapter->nr && !i2c_new_device(adapter, &devinfo->board_info))
			dev_err(&adapter->dev, "Can't create device at 0x%02x\n", devinfo->board_info.addr);
	}
	up_read(&__i2c_board_lock);
}

​   该函数就是遍历3.4中讲到的,通过遍历__i2c_board_list总链表,将开发人员自己添加的从设备信息转换成i2c_client,并注册从设备。

5.3、i2c_register_driver

int i2c_register_driver(struct module *owner, struct i2c_driver *driver)
{
    ......
	driver->driver.owner = owner;
	driver->driver.bus = &i2c_bus_type;
    ......
	res = driver_register(&driver->driver);
	INIT_LIST_HEAD(&driver->clients);
	......
	i2c_for_each_dev(driver, __process_new_driver);
}

5.3.1、driver_register

​   该函数就大概讲下,它内部函数的大致调用流程,driver_register()–>bus_add_driver()–>driver_attach()–>bus_for_each_dev()–>__driver_attach()–>driver_match_device() 和 driver_probe_device() 。driver_match_device 函数最终会调用i2c_bus_type上的 match函数,也就是i2c_device_match 函数。driver_probe_device 函数最终会调用i2c_bus_type上的 probe函数,也就是i2c_device_probe 函数。

5.3.2、i2c_for_each_dev

​   此处 bus_for_each_dev(),会遍历 i2c 总线 i2c_bus_type 设备链表上的每个设备, 对每个设备调用__process_new_driver. ,该函数具体如下:

int __process_new_driver(struct device *dev, void *data)
{
    //对适配器进行相关处理
    if (dev->type != &i2c_adapter_type)
   	 	return 0;
    return i2c_do_add_adapter(data, to_i2c_adapter(dev));	//展开见5.2.2
}

可以看出,他遍历的其实是 i2c 总线 i2c_bus_type 上的 IIC 适配器设备。具体说明可以看下5.2.2

5.4、i2c_transfer

int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)
{
	int ret;
	......
	if (adap->algo->master_xfer) 									//判断适配器是否定义了传输函数
    {
        ......
		ret = __i2c_transfer(adap, msgs, num);
        {
            unsigned long orig_jiffies;
            int ret, try;
            ......
            orig_jiffies = jiffies;
            for (ret = 0, try = 0; try <= adap->retries; try++) 		//传输次数,在3.1.3中讲到
            {
                ret = adap->algo->master_xfer(adap, msgs, num);			//调用适配器传输函数
                if (ret != -EAGAIN)
                    break;
                if (time_after(jiffies, orig_jiffies + adap->timeout))	//传输超时时间判断
                    break;
            }
        }
        ......
		return ret;
	}
}

master_xfer(adap, msgs, num)函数就与Soc 的 IIC 控制器寄存器设置相关了,基本是裸机相关的内容,无非就是产生 IIC 通信时序,如开始信号,停止信号、应答信号等。

5.5、i2c_dev_init

static int __init i2c_dev_init(void)
{
	int res;
	res = register_chrdev(I2C_MAJOR, "i2c", &i2cdev_fops);
	i2c_dev_class = class_create(THIS_MODULE, "i2c-dev");
	i2c_dev_class->dev_groups = i2c_groups;
    ......
	i2c_for_each_dev(NULL, i2cdev_attach_adapter);
	......
	return 0;
}

​   该函数定义在 drivers\i2c\i2c-dev.c 目录下,i2c-dev.c文件为我们提供了一种应用层操作从设备的 IIC 方式,以前我们都是在内核注册从设备驱动,然后内核中调用 IIC 控制器的通信函数,而i2c-dev.c则可以让我们在应用层操作该 IIC 总线上的任意从设备。可以这么理解,在内核创建的驱动主体指从设备,在i2c-dev.c中创建的驱动主体则是指 IIC 控制器本身。

​   register_chrdev 函数注册了一个 IIC 字符设备,主设备号为 I2C_MAJOR。然后创建了一个"i2c-dev"类,这个就是/sys/class/i2c-dev目录的由来,而 i2c_groups 里面包含了该类中专有的属性文件。在/sys/class/i2c-dev目录下会有i2c-%d文件,这个文件可不是指前面注册适配器设备中生成,而是通过i2c_for_each_dev函数,遍历当前所有的适配器,调用i2cdev_attach_adapter函数,完成i2c-%d文件的创建。因为已经注册了 IIC 字符设备,所以这些适配器也会在/dev目录下自动生成设备节点,这样就可以操作/dev的设备节点,来完成对该设备下的所有从设备的操作。

5.6、i2c_master_send,i2c_master_recv

​   这两个函数就是在应用层 可以通过 read和write函数 来操作 IIC 控制器接收和发送的原因,它们本质也就是对 i2c_transfer 函数的封装。

int i2c_master_send(const struct i2c_client *client, const char *buf, int count)
{
	int ret;
	struct i2c_adapter *adap = client->adapter;
	struct i2c_msg msg;

	msg.addr = client->addr;
	msg.flags = client->flags & I2C_M_TEN;
	msg.len = count;
	msg.buf = (char *)buf;

	ret = i2c_transfer(adap, &msg, 1);

	return (ret == 1) ? count : ret;
}
int i2c_master_recv(const struct i2c_client *client, char *buf, int count)
{
	struct i2c_adapter *adap = client->adapter;
	struct i2c_msg msg;
	int ret;

	msg.addr = client->addr;
	msg.flags = client->flags & I2C_M_TEN;
	msg.flags |= I2C_M_RD;
	msg.len = count;
	msg.buf = buf;
	ret = i2c_transfer(adap, &msg, 1);
    
	return (ret == 1) ? count : ret;
}

​   这里简单说下怎么在应用层操作从设备的方法,首先通过 open 函数打开一个设备,然后通过ioctl设置你要操作的从设备地址,当然ioctl函数不仅可以设置从设备地址,还可以设置你的重传次数,超时时间等,最后就可以通过 write 和 read 函数接收和发送。

6、参考资料

​ 1、Linux IIC 驱动分析 — 框架分析

​ 2、fs4412 I2C驱动基于Cortex-A9,mpu6050裸机程序,驱动,I2C架构,有这一篇够了

​ 3、Linux内核I2C子系统初始化驱动架构 (网上下载的CSDN资源,我也不清楚我这是哪来的pdf文档了)

你可能感兴趣的:(linux,c语言,mcu)