(注: 基于beagle bone green开发板,linux4.14内核版本)html
在本系列linux内核i2c框架的前两篇,分别讲了:
linux设备驱动程序-i2c(0)-i2c设备驱动源码实现
linux设备驱动程序-i2c(1):i2c总线的添加与实现node
而在linux设备驱动程序--串行通讯驱动框架分析中,讲到linux内核中串行通讯驱动框架大致分为三层:linux
在上一章节咱们讲了整个总线的实现以及device和driver的匹配机制,这一章节咱们要来说讲i2c硬件读写层的实现。编程
咱们来回顾一下,在本系列文章的第一章linux设备驱动程序-i2c(0)-i2c设备驱动源码实现源码中是怎么使用i2c和设备进行通讯的呢?
一、首先,在总线的device部分,使用框架
struct i2c_adapter *adap = i2c_get_adapter(2)
这个接口,获取一个struct i2c_adapter结构体指针,并关联到i2c_client中。函数
二、而后,在总线driver的probe部分,在/dev目录下建立文件,并关联对应的file_operations结构体。spa
三、在file_operations结构体的write函数中,使用指针
s32 i2c_smbus_write_byte_data(const struct i2c_client *client,u8 command,u8 value);
这个接口,直接向i2c设备中写数据(command和value)。code
四、 而第三点中i2c_client就是device源码部分注册到bus中的i2c_client,且包含对应的adapter,同时包含i2c地址,设备名等信息。orm
若是再往深挖一层,会发现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时序,实现数据收发,且维护收发buf,对外提供寄存器接口。
硬件控制器这一类外设通常直接挂在CPU总线上,CPU可直接寻址访问。
当主机须要经过i2c接口收发数据时,直接经过读写硬件i2c控制器寄存器便可,硬件控制器会将主机传送过来的数据自动完成发送,接收到的数据直接放在buf中供主机读取。
(注:在源码示例中,博主使用的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描述一个硬件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的添加流程已经梳理完成,回到咱们以前的问题:
用于实际通讯中的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不沾身.