linux i2c驱动?linux平板

这篇文章给大家聊聊关于linux i2c驱动,以及linux平板对应的知识点,希望对各位有所帮助,不要忘了收藏本站哦。

手把手教你写Linux驱动之模拟I2C&e2prom

在深入Linux驱动开发之前,让我们先来了解一些基础知识。Linux驱动开发是嵌入式系统开发中重要的一环,它涉及到硬件资源的管理和控制。如果你对驱动开发感到好奇,或者在寻求百万年薪的机会,本文将为你提供一个入门的指南。

驱动开发的主要语言是C,它在硬件与操作系统之间架起桥梁,管理硬件资源。Linux驱动与Windows或iOS驱动在功能上可能有所不同,但对硬件的控制逻辑通常保持一致。Linux驱动开发的吸引力在于其广泛应用,无论是桌面系统、服务器还是移动设备。

为了让你更直观地理解Linux驱动开发的过程,我们将教你如何编写一个模拟的I2C控制器和e2prom访问驱动。这个驱动既不完全模拟总线协议,也不完全忽略协议细节,而是采取一种折中的方式来实现。所有代码示例和跟踪仓库均可在GitHub上找到。

I2C总线是常见的嵌入式通信总线,e2prom是一种非易失性存储,用于设备信息的持久化存储。我们的驱动将模拟这些硬件组件,让你在Linux环境下进行实验,无需实际的硬件平台。

在Linux环境中,如deepin操作系统,你可以轻松地进行编码和实验。一个快速的代码敲打和清晰的思维是成功的关键。为了实现模拟I2C控制器和e2prom的访问,我们将构建一个完整的驱动框架,包括Makefile的编写。

构建一个驱动框架需要考虑的几个关键点包括:选择合适的编辑器(如vim),理解Linux下的基础命令和路径概念(如pwd、ls等)。在主Makefile中,我们将编写一个脚本,用于遍历并编译各个驱动模块。

接下来,我们将在一个目录中创建四个子目录,分别用于存放设备和驱动的相关代码。每个子目录将包含一个Makefile和源代码文件。在编写代码时,我们将遵循Linux驱动开发的最佳实践,确保代码结构清晰、易于维护。

在模拟I2C控制器的驱动开发过程中,我们将编写一个设备(Dev)和一个驱动(Drv)模块。每个模块都将遵循Linux驱动框架,包括初始化、注册和卸载等关键步骤。通过实践,你将深入了解Linux驱动开发的流程,并能够独立编写驱动代码。

最终,我们将展示一个完整的驱动开发流程,从Makefile的编写到驱动模块的加载和验证。通过本指南,你将掌握Linux驱动开发的基础知识和实践技巧,为你的嵌入式系统开发之旅打下坚实的基础。

Linux GPIO模拟I2C驱动

无论是Linux系统还是单片机,GPIO模拟I2C均通过控制SDA和SCL线的电平高低来产生信号。

主要包括模拟起始信号、停止信号,以及读写信号。

这属于platform驱动,而非I2C设备驱动。

在Linux系统中,无论是x86还是arm架构,都可以直接使用现成的i2c-gpio.ko驱动来实现GPIO模拟I2C Bus。

加载该驱动后,可以通过i2cdetect命令扫描总线下存在的I2C设备。

驱动分析:

1.判断使用of或者platform获取GPIO信息,例如of,使用of_property_read_u32和of_property_read_bool函数获取dts配置的数据。

2.设置GPIO模式,通过i2c_gpio_get_desc调用devm_gpiod_get或devm_gpiod_get_index函数获取资源。

3.设置GPIO读写函数,包括sda读写和scl读写,时钟设置等。

4.设置I2C bus时钟和timeout。

5.添加I2C bus,包括添加i2c_bit_algo,具体实现I2C start、data、stop等功能,无需i2c-gpio.c实现。

类似单价需要自行实现IIC_Start和IIC_Stop等操作。

2.添加add_adapter,i2c_bit_algo->bit_xfer函数根据I2C协议规定,实现发送起始信号->数据传输->停止信号的过程。

linux设备驱动程序——i2c总线的添加与实现

一文看懂linux内核详解

深入了解使用linux查看磁盘io使用情况

在实际驱动开发过程中,i2c总线也是集成在系统中的,驱动开发者不需要关心i2c总线的初始化,只需要将相应的i2c_client和i2c_driver挂载在总线上进行匹配即可。

那么,i2c总线在系统中是如何初始化得来的呢?

答案就在文件i2c-core-base.c中,它的过程是这样的:

在i2c_init函数中,使用bus_register()将i2c总线注册到系统中,那么这个i2c_init()函数是在哪里被调用的呢?

在内核启动的过程中,进入C语言环境的第一个入口函数是start_kernel(),但是i2c_init()并非由start_kernel()间接调用,而是借助于linux内核中的initcall机制被调用,简而言之,就是将这个函数地址在编译阶段放入特定的段中,在内核初始化时由某个启动函数将这些段中的函数一一取出并执行。

i2c总线通过postcore_initcall()将init函数注册到系统中。

当模块被加载进系统时,就会执行i2c_init函数来进行初始化。

在i2c_bus_type结构体中,定义了match(),probe(),remove()和shutdown()函数,match()函数就是当有新的i2c_client或者i2c_driver添加进来时,试图寻找与新设备匹配的项,返回匹配的结果。

remove()和shutdown(),顾名思义,这两个函数是管理的驱动移除行为的。

对于probe函数,在之前的章节中提到:当相应的device和driver经由总线匹配上时,会调用driver部分提供的probe函数。

那么:

带着这两个疑问,我们接着往下看。

在这里我们不免要回顾一下前面章节所说的,作为一个驱动开发者,如果我们要开发某些基于i2c的设备驱动,需要实现的框架流程是怎样的:

但是问题是,为什么device和driver都注册进去之后,就会调用driver部分提供的probe函数呢?

为了解决这些问题,最好的办法就是看源代码,假设我们是以设备树的方式进行匹配,device(即i2c_client)部分已经被注册到系统中,此时我们向系统中注册(insmod由driver部分程序编译的模块)相应的driver部分,接下来我们跟踪driver部分注册到i2c总线的流程,看看它是怎么实现的:

跟踪i2c_add_driver:

经过一系列的代码跟踪,找到了bus_add_driver(),根据名称可以知道,这个函数就是将当前i2c_driver添加到i2c_bus_type(即i2c总线)中。

接着往下看:

从第一部分可以看到,将当前drv链入到bus->p->klist_drivers链表中,那么可以猜到,在注册device部分的时候,就会将device链入到bus->p->klist_devices中。

然后,再看driver_attach(drv):

driver_attach调用bus_for_each_dev,传入当前驱动bus(即i2c_bus_type),当前驱动drv,以及一个函数__driver_attach。

bus_for_each_dev对每个device部分调用__driver_attach。

在__driver_attach中,对每个drv和dev调用driver_match_device(),并根据函数返回值决定是否继续执行调用driver_probe_device()。

从函数名来看,这两个函数大概就是我们在前文中提到的match和probe函数了,我们不妨再跟踪看看,先看看driver_match_device()函数:

果然不出所料,这个函数十分简单,如果当前驱动的所属的bus有相应的match函数,就调用match函数,否则返回1.

当前driver所属的总线为i2c_bus_type,根据上文i2c总线的初始化部分可以知道,i2c总线在初始化时提供了相应的match函数,所以,总线的match函数被调用,并返回匹配的结果。

接下来我们再看看driver_probe_device()函数:

在driver_probe_device中又调用了really_probe,在really_probe()中,先判断当前bus总线中是否注册probe()函数如果有注册,就调用总线probe函数,否则,就调用当前drv注册的probe()函数。

到这里,我们解决了上一节中的一个疑问:总线和driver部分都有probe函数时,程序是怎么处理的?

答案已经显而易见了:优先调用总线probe函数。

而且我们理清了总线的match()函数和probe()函数是如何被调用的。

那么,上一节中还有一个疑问没有解决:总线的match和probe函数执行了一些什么行为?

对于总线probe函数,获取匹配成功的device,再由device获取driver,优先调用driver->probe_new,因为driver中没有设置,直接调用driver的probe函数。

总线probe和driver的probe函数的关系就是:当match返回成功时,优先调用总线probe,总线probe完成一系列的初始化,再调用driver的probe函数,如果总线probe函数不存在,就直接调用driver的probe函数,所以当我们在系统中添加了相应的device和driver部分之后,driver的probe函数就会被调用。

对于总线match函数,我们直接查看在i2c总线初始化时的函数定义:

i2c_device_match就是i2c_driver与i2c_device匹配的部分,在i2c_device_match函数中,可以看到,match函数并不只是提供一种匹配方式:

接下来再深入函数内部,查看匹配的细节部分:

在i2c_of_match_device中,调用了of_match_device()和i2c_of_match_device_sysfs()两个函数,这两个函数代表了两种匹配方式,先来看看of_match_device:

of_match_device调用of_match_node。

of_match_node调用__of_match_node函数。

如果你对设备树有一定的了解,就知道系统在初始化时会将所有设备树子节点转换成由struct device_node描述的节点。

在被调用的__of_match_node函数中,对device_node中的compatible属性和driver部分的of_match_table中的compatible属性进行匹配,由于compatible属性可以设置多个,所以程序中会对其进行逐一匹配。

我们再回头来看设备树匹配方式中的i2c_of_match_device_sysfs()匹配方式:

由i2c_of_match_device_sysfs()的实现可以看出:当设备树中不存在设备节点时,driver部分的of_match_table中的compatible属性试图去匹配i2c_client(device部分)的.driver.name属性.

因为设备树的默认规则,compatible属性一般形式为"vender_id,product_id",当compatible全字符串匹配不成功时,取product_id部分再进行匹配,这一部分主要是兼容之前的不支持设备树的版本。

acpi匹配方式较为少用且较为复杂,这里暂且不做详细讨论

id_table匹配方式中,这种匹配方式一目了然,就是对id_table(driver部分)中的.name属性和i2c_client(device部分)的.name属性进行匹配。那么i2c_client的.name是怎么来的呢?

在非设备树的匹配方式中,i2c_client的.name属性由驱动开发者指定,而在设备树中,i2c_client由系统对设备树进行解析而来,i2c_client的name属性为设备树compatible属性"vender_id,product_id"中的"product_id",所以,在进行匹配时,默认情况下并不会严格地要求 of_match_table中的compatible属性和设备树中compatible属性完全匹配,driver中.drv.name属性和.id.name属性也可以与设备树中的"product_id"进行匹配。

关于linux i2c总线的初始化以及运行机制的讨论就到此为止啦

阅读剩余
THE END