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. 构建一个给爬虫使用的代理IP池

    做网络爬虫时,一般对代理IP的需求量比较大.因为在爬取网站信息的过程中,很多网站做了反爬虫策略,可能会对每个IP做频次控制.这样我们在爬取网站时就需要很多代理IP. 代理IP的获取,可以从以下几个途径 ...

  2. Android Studio 之 控件基础知识

    1. TextView 和 EditText 控件常用属性  android:layout_width="match_parent" 宽度与父控件一样宽 android:layou ...

  3. Spring Boot 排除自动配置的 4 种方法,关键时刻很有用!

    Spring Boot 提供的自动配置非常强大,某些情况下,自动配置的功能可能不符合我们的需求,需要我们自定义配置,这个时候就需要排除/禁用 Spring Boot 某些类的自动化配置了. 比如:数据 ...

  4. DI 依赖注入之unity的MVC版本使用Microsoft.Practices.Unity1.2与2.0版本对比

    DI 依赖注入之unity的MVC版本使用Microsoft.Practices.Unity1.2与2.0版本对比 参考:https://www.cnblogs.com/xishuai/p/36702 ...

  5. eclipse.ini相关问题

    一般新装的eclipse,在eclipse.ini文件中,有设置默认的内存信息,如果你要开发一个大的项目或者导入大的项目,那么,eclipse就会时不时报出这样的错误:An internal erro ...

  6. SpringBoot扩展点之一:SpringApplicationRunListener

    三种监听器的关系 ApplicationListener.SpringApplicationRunListeners.SpringApplicationRunListener的关系: SpringAp ...

  7. Sitecore安全:访问权限

    由于Sitecore使用Core数据库中的项来定义其用户界面,因此您可以对该数据库中的项应用访问权限,以控制对CMS功能的访问.最常见的是,将用户置于预定义的Sitecore客户端角色中 Siteco ...

  8. TP5接口开发之异常处理接管

    前几天在开发的时候用到了第三方的扩展包,使用过程中第三方扩展包抛出了异常 因为这边是接口开发,需要返回错误代码以及提示信息等,所以就需要接管异常处理. 此文章只做笔记,有不对或不详细的地方欢迎大家留言 ...

  9. 关于 Windows to go

    1. 在宿主计算器的操作系统中访问 Windows to go 的磁盘 如题,如果需要在宿主计算器的操作系统中访问 Windows to go 的U盘(移动硬盘)中的文件,只需要打开磁盘管理,“更改驱 ...

  10. 『正睿OI 2019SC Day6』

    动态规划 \(dp\)早就已经是经常用到的算法了,于是老师上课主要都在讲题.今天讲的主要是三类\(dp\):树形\(dp\),计数\(dp\),\(dp\)套\(dp\).其中计数\(dp\)是我很不 ...