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不沾身.

linux设备驱动程序-i2c(2)-adapter和设备树的解析的更多相关文章

  1. linux设备驱动程序-i2c(1):i2c总线的添加与实现

    linux设备驱动程序-i2c(1):i2c总线的添加与实现 (基于4.14内核版本) 在上一章节linux设备驱动程序-i2c(0)-i2c设备驱动源码实现中,我们演示了i2c设备驱动程序的源码实现 ...

  2. linux设备驱动程序-i2c(0)-i2c设备驱动源码实现

    (基于4.14内核版本) 为了梳理清楚linux内核中的i2c实现框架,从本文开始,博主将分几个章节分别解析i2c总线在linux内核中的形成过程.匹配过程.以及设备驱动程序源码实现. 在介绍linu ...

  3. LINUX设备驱动程序笔记(一)设备驱动程序简单介绍

    <一>:设备驱动程序的作用 从一个角度看,设备驱动程序的作用在于提供机制,而不是策略. 在编写驱动程序时,程序猿应该特别注意以下这个基本概念:编写訪问硬件的内核代码时,不要给用户强加不论什 ...

  4. 简单linux块设备驱动程序

    本文代码参考<LINUX设备驱动程序>第十六章 块设备驱动程序 本文中的“块设备”是一段大小为PAGE_SIZE的内存空间(两个扇区,每个扇区512字节) 功能:向块设备中输入内容,从块设 ...

  5. 简单linux字符设备驱动程序

    本文代码参考<LINUX设备驱动程序>第三章 字符设备驱动程序 本文中的“字符设备”是一段大小为PAGE_SIZE的内存空间 功能:向字符设备写入字符串:从字符设备读出字符串 代码: 1. ...

  6. 如何编写Linux设备驱动程序

    一.Linux device driver 的概念 系统调用是操作系统内核和应用程序之间的接口,设备驱动程序是操作系统内核和机器硬件之间的接口.设备驱动程序为应用程序屏蔽了硬件的细节,这样在应用程序看 ...

  7. 《Linux Device Drivers》第十六章 块设备驱动程序——note

    基本介绍 块设备驱动程序通过主传动固定大小数据的随机访问设备 Linux核心Visual块设备作为基本设备和不同的字符设备类型 Linux块设备驱动程序接口,使块设备最大限度地发挥其效用.一个问题 一 ...

  8. Linux内核入门到放弃-设备驱动程序-《深入Linux内核架构》笔记

    I/O体系结构 总线系统 PCI(Peripheral Component Interconnect) ISA(Industrial Standard Architecture) SBus IEEE1 ...

  9. 深入理解Linux内核-I/O体系结构和设备驱动程序

    系统总线:1.链接CPU.RAM.I/O设备之间的数据流动.例如:PCI.ISA.EISA.MCA.SCSI.USB2.任何I\O设备有且仅能链接一条总线. I\O端口:1.每个连接到I\O总线上的设 ...

随机推荐

  1. 洛谷 P3368 【模板】树状数组 2 题解

    P3368 [模板]树状数组 2 题目描述 如题,已知一个数列,你需要进行下面两种操作: 1.将某区间每一个数数加上x 2.求出某一个数的值 输入格式 第一行包含两个整数N.M,分别表示该数列数字的个 ...

  2. [no_perms] Private mode enable, only admin can publish this module

    在使用npm publish是出现了错误: npm ERR! code E403 npm ERR! 403 Forbidden - PUT https://registry.npm.taobao.or ...

  3. org.Hs.eg.db

    bioconduction 主页 http://www.bioconductor.org/packages/release/data/annotation/html/org.Hs.eg.db.html ...

  4. UDF——输出每个单元的面法向量以及对应面上的节点

    测试文件及源码下载链接: https://pan.baidu.com/s/1K-mD7-_ZkHUl21C2w3o-Bw 提取码: a7n2

  5. jQuery前端生成二维码

    引用: <script src="assets/js/jquery.qrcode.min.js" charset="UTF-8"></scri ...

  6. prometheus、node_exporter、cAdvisor常用参数

    本节将介绍一下我在使用过程中用到的promethues.node_exporter.cAdvisor的常用参数,做一个总结 一.prometheus prometheus分为容器安装和二进制文件安装, ...

  7. 去除img标签函数

    需要去除一个长字符串中的img标签,网上找到了这个代码试试看,确实是有效的.代码如下: <?php function strip_tags_img($string='') { $pattern= ...

  8. Appium Grid并发测试

    背景 Selenium玩的比较6的同学比较清楚:在Selenium中三大组件中有包含了Selenium Grid,而其作用就是分布式执行测试用例.主要的应用场景在于: 缩短测试执行时间,提高自动化测试 ...

  9. c++ builder调用sql server的存储过程进行数据的下载和上传

    小小的几行代码,在这里搞了一天.好好的一个周六过的无比的难受.代码很简单,但是主要原因是因为在用合作商的软件上传数据的时候有些框框没有勾选. come on....... 1.用两个控件ADOConn ...

  10. jquery swipper插件的一些弊端

    jquery swipper插件的一些弊端touch触摸机制是swipper的 阻止click冒泡.拖动Swiper时阻止click事件.下面这个方法或许可以解决触摸机制的问题 <pre> ...