stm32mp157  盘古开发板  Linux内核版本4.19

目录

1、朱有鹏老师视频笔记

2、I2C子系统的4个关键结构体

3、关键文件

4、i2c-core.c初步分析

​4.1、smbus代码略过

4.2、模块加载和卸载:bus_register(&i2c_bus_type);    在i2c-core-base.c中

4.3、I2C总线的匹配机制

​4.3.1、match函数

4.3.2、probe函数

4.4、核心层开放给其他部分的注册接口

5、adapter模块

5.1、adapter模块的注册

5.1.1、平台总线方式注册

​5.1.2、找到driver和device,并且确认其配对过程

5.2、probe函数

6、i2c_driver的注册

7、i2c_client从哪里来


1、朱有鹏老师视频笔记

***********《朱有鹏老师嵌入式linux核心课程》 ***********
《5.linux驱动开发-第9部分-5.9.触摸屏驱动移植实战》--------------------------------------------------------
本课程由朱老师物联网大讲堂推出并提供技术支持,课件可打包下载
网盘地址:http://yunpan.cn/cjVy3RAgfDufK 访问密码 4ad7
技术交流QQ群:朱老师物联网讲堂1群 397164505
--------------------------------------------------------第一部分、章节目录
5.9.1.触摸屏驱动概览
5.9.2.内核中的竞争状态和互斥1
5.9.3.内核中的竞争状态和互斥2
5.9.4.中断的上下半部1
5.9.5.中断的上下半部2
5.9.6.linux内核的I2C子系统详解1
5.9.7.linux内核的I2C子系统详解2
5.9.8.linux内核的I2C子系统详解3
5.9.9.linux内核的I2C子系统详解4
5.9.10.linux内核的I2C子系统详解5
5.9.11.linux内核的I2C子系统详解6
5.9.12.linux内核的I2C子系统详解7
5.9.13.gslX680驱动的移植实践
5.9.14.gslX680驱动源码分析2
5.9.15.gslX680驱动源码分析2
5.9.16.老版本触摸屏的驱动第二部分、章节介绍
5.9.1.触摸屏驱动概览本节主要介绍了2种触摸屏的驱动差别,本课程用到的三个版本开发板的触摸屏差别以及学习触摸屏驱动的关键点和学习方法。
5.9.2.内核中的竞争状态和互斥1本节将前面课程中提到,但是没有详细总结的内核竟态处理方法如原子操作、互斥锁、自旋锁等做了详细系统的总结。
5.9.3.内核中的竞争状态和互斥2本节将前面课程中提到,但是没有详细总结的内核竟态处理方法如原子操作、互斥锁、自旋锁等做了详细系统的总结。
5.9.4.中断的上下半部1本节主要讲了linux内核中中断上下半部的思路,以及三种下半部解决方案。
5.9.5.中断的上下半部2本节主要通过代码实例来演示tasklet、workqueue等中断下半部的处理,目的是希望大家掌握这套处理流程,在后面分析触摸屏驱动时看到这些代码能更容易的理解。
5.9.6.linux内核的I2C子系统详解1本节主要对I2C总线的特征做了个汇总,并专门就一些理解的关键点指出说明。
5.9.7.linux内核的I2C子系统详解2 本节对内核的I2C子系统的构建和组成部分做了整体说明。
5.9.8.linux内核的I2C子系统详解3本节讲了I2C系统的四个关键结构体,理解这四个结构体及其关联对理解I2C系统的工作原理至关重要。
5.9.9.linux内核的I2C子系统详解4本节带大家分析i2c_core.c的源码,该文件主要为其他部分提供各种注册接口,属于I2C子系统的关键性代码块。
5.9.10.linux内核的I2C子系统详解5本节以S5PV210芯片的i2c_adapter驱动为例,详细分析了主机SoC的I2C控制器部分的驱动源码及其实现细节。
5.9.11.linux内核的I2C子系统详解6本节分析了i2c_driver部分的细节,包括driver的匹配方式、probe函数调用的过程分析等
5.9.12.linux内核的I2C子系统详解7本节分析了i2c_client的来源,包括board_info如何在mach文件注册,如何通过i2c_new_device接口将其转为i2c_client结构体。
5.9.13.gslX680驱动的移植实践本节将提供的gslX680的驱动源码移植到老版本内核中并且添加必要的配置,使之正常工作,这种移植工作以后在工作中用到的可能性很大。
5.9.14.gslX680驱动源码分析2本节开始分析gslX680的驱动源码,主要是i2c_driver和client端的匹配、i2c_client的创建和数据传递这些。
5.9.15.gslX680驱动源码分析2本节接着分析gslX680的驱动源码,主要是probe函数中的操作,以及中断下半部中上报坐标数据等内容。
5.9.16.老版本触摸屏的驱动    本节对老版本开发板中ft5x05的驱动源码进行了分析,以及移植方面的实践操作。通过2款触摸屏的驱动对比,让大家思路更加宽广。第三部分、随堂记录
5.9.1.触摸屏驱动概览
5.9.1.1、常用的2种触摸屏
(1)电阻触摸屏。驱动一般分2种:一种是SoC内置触摸屏控制器,一种是外置的专门触摸屏控制芯片,通过I2C接口和SoC通信。
(2)电容触摸屏。驱动只有一种,外接专用的电容式触摸屏控制芯片,I2C接口和SoC通信。
5.9.1.2、X210使用的触摸屏
(1)X210V3使用的触摸屏:ft5x06
(2)X210V3S使用的触摸屏:gslX680
5.9.1.3、学习触摸屏驱动的关键点
(1)input子系统相关知识
(2)中断上下半部
(3)I2C子系统
(4)触摸屏芯片本身知识
5.9.1.4、竞争状态和同步5.9.2_3.内核中的竞争状态和互斥1_2
5.9.2.1、一些概念
(1)竞争状态(简称竟态)
(2)临界段、互斥锁、死锁
(3)同步(多CPU、多任务、中断)
5.9.2.2、解决竟态的方法
(1)原子操作 automic_t
(2)信号量、互斥锁
(3)自旋锁
5.9.2.3、自旋锁和信号量的使用要点
(1)自旋锁不能递归
(2)自旋锁可以用在中断上下文(信号量不可以,因为可能睡眠),但是在中断上下文中获取自旋锁之前要先禁用本地中断
(3)自旋锁的核心要求是:拥有自旋锁的代码必须不能睡眠,要一直持有CPU直到释放自旋锁
(4)信号量和读写信号量适合于保持时间较长的情况,它们会导致调用者睡眠,因此只能在进程上下文使用,而自旋锁适合于保持时间非常短的情况,它可以在任何上下文使用。如果被保护的共享资源只在进程上下文访问,使用信号量保护该共享资源非常合适,如果对共享资源的访问时间非常短,自旋锁也可以。但是如果被保护的共享资源需要在中断上下文访问(包括底半部即中断处理句柄和顶半部即软中断),就必须使用自旋锁。自旋锁保持期间是抢占失效的,而信号量和读写信号量保持期间是可以被抢占的。自旋锁只有在内核可抢占或SMP(多处理器)的情况下才真正需要,在单CPU且不可抢占的内核下,自旋锁的所有操作都是空操作。5.9.4.中断的上下半部1
5.9.4.1、中断处理的注意点
(1)中断上下文,不能和用户空间数据交互
(2)不能交出CPU(不能休眠、不能schedule)
(3)ISR运行时间尽可能短,越长则系统响应特性越差
5.9.4.2、中断下半部2种解决方案
(1)为什么要分上半部(top half,又叫顶半部)和下半部(bottom half,又叫底半部)
(2)下半部处理策略1:tasklet(小任务)
(3)下半部处理策略2:workqueue(工作队列)
5.9.4.3、tasklet使用实战
(1)tasklet相关接口介绍
(2)实战演示tasklet实现下半部5.9.5.中断的上下半部2
5.9.5.1、workqueue实战演示
(1)workqueue的突出特点是下半部会交给worker thead,因此下半部处于进程上下文,可以被调度,因此可以睡眠。
(2)include/linux/workqueue.h5.9.5.2、中断上下半部处理原则
(1)必须立即进行紧急处理的极少量任务放入在中断的顶半部中,此时屏蔽了与自己同类型的中断,由于任务量少,所以可以迅速不受打扰地处理完紧急任务。
(2)需要较少时间的中等数量的急迫任务放在tasklet中。此时不会屏蔽任何中断(包括与自己的顶半部同类型的中断),所以不影响顶半部对紧急事务的处理;同时又不会进行用户进程调度,从而保证了自己急迫任务得以迅速完成。
(3)需要较多时间且并不急迫(允许被操作系统剥夺运行权)的大量任务放在workqueue中。此时操作系统会尽量快速处理完这个任务,但如果任务量太大,期间操作系统也会有机会调度别的用户进程运行,从而保证不会因为这个任务需要运行时间将其它用户进程无法进行。
(4)可能引起睡眠的任务放在workqueue中。因为在workqueue中睡眠是安全的。在需要获得大量的内存时、在需要获取信号量时,在需要执行阻塞式的I/O操作时,用workqueue很合适。5.9.6_7.linux内核的I2C子系统详解1_2
5.9.6.1、I2C总线汇总概览
(1)三根通信线:SCL、SDA、GND
(2)同步、串行、电平、低速、近距离
(3)总线式结构,支持多个设备挂接在同一条总线上
(4)主从式结构,通信双方必须一个为主(master)一个为从(slave),主设备掌握每次通信的主动权,从设备按照主设备的节奏被动响应。每个从设备在总线中有唯一的地址(slave address),主设备通过从地址找到自己要通信的从设备(本质是广播)。
(5)I2C主要用途就是主SoC和外围设备之间的通信,最大优势是可以在总线上扩展多个外围设备的支持。常见的各种物联网传感器芯片(如gsensor、温度、湿度、光强度、酸碱度、烟雾浓度、压力等)均使用I2C接口和主SoC进行连接。
(6)电容触摸屏芯片的多个引脚构成2个接口。一个接口是I2C的,负责和主SoC连接(本身作为从设备),主SoC通过该接口初始化及控制电容触摸屏芯片、芯片通过该接口向SoC汇报触摸事件的信息(触摸坐标等),我们使用电容触摸屏时重点关注的是这个接口;另一个接口是电容触摸板的管理接口,电容触摸屏芯片通过该接口来控制触摸板硬件。该接口是电容触摸屏公司关心的,他们的触摸屏芯片内部固件编程要处理这部分,我们使用电容触摸屏的人并不关心这里。
5.9.6.2、linux内核的I2C驱动框架总览
(1)I2C驱动框架的主要目标是:让驱动开发者可以在内核中方便的添加自己的I2C设备的驱动程序,从而可以更容易的在linux下驱动自己的I2C接口硬件
(2)源码中I2C相关的驱动均位于:drivers/i2c目录下。linux系统提供2种I2C驱动实现方法:第一种叫i2c-dev,对应drivers/i2c/i2c-dev.c,这种方法只是封装了主机(I2C master,一般是SoC中内置的I2C控制器)的I2C基本操作,并且向应用层提供相应的操作接口,应用层代码需要自己去实现对slave的控制和操作,所以这种I2C驱动相当于只是提供给应用层可以访问slave硬件设备的接口,本身并未对硬件做任何操作,应用需要实现对硬件的操作,因此写应用的人必须对硬件非常了解,其实相当于传统的驱动中干的活儿丢给应用去做了,所以这种I2C驱动又叫做“应用层驱动”,这种方式并不主流,它的优势是把差异化都放在应用中,这样在设备比较难缠(尤其是slave是非标准I2C时)时不用动驱动,而只需要修改应用就可以实现对各种设备的驱动。这种驱动在驱动层很简单(就是i2c-dev.c)我们就不分析了。
(3)第二种I2C驱动是所有的代码都放在驱动层实现,直接向应用层提供最终结果。应用层甚至不需要知道这里面有I2C存在,譬如电容式触摸屏驱动,直接向应用层提供/dev/input/event1的操作接口,应用层编程的人根本不知道event1中涉及到了I2C。这种是我们后续分析的重点。5.9.8.linux内核的I2C子系统详解3
5.9.8.1、I2C子系统的4个关键结构体
(1)struct i2c_adapter           I2C适配器
(2)struct i2c_algorithm         I2C算法
(3)struct i2c_client            I2C(从机)设备信息
(4)struct i2c_driver            I2C(从机)设备驱动
5.9.8.2、关键文件
(1)i2c-core.c
(2)busses目录
(3)algos目录5.9.9.linux内核的I2C子系统详解4
5.9.9.1、i2c-core.c初步分析
(1)smbus代码略过
(2)模块加载和卸载:bus_register(&i2c_bus_type);
5.9.9.2、I2C总线的匹配机制
(1)match函数
(2)probe函数
总结:I2C总线上有2条分支:i2c_client链和i2c_driver链,当任何一个driver或者client去注册时,I2C总线都会调用match函数去对client.name和driver.id_table.name进行循环匹配。如果driver.id_table中所有的id都匹配不上则说明client并没有找到一个对应的driver,没了;如果匹配上了则标明client和driver是适用的,那么I2C总线会调用自身的probe函数,自身的probe函数又会调用driver中提供的probe函数,driver中的probe函数会对设备进行硬件初始化和后续工作。
5.9.9.3、核心层开放给其他部分的注册接口
(1)i2c_add_adapter/i2c_add_numbered_adapter     注册adapter的
(2)i2c_add_driver                               注册driver的
(3)i2c_new_device                               注册client的5.9.10.linux内核的I2C子系统详解5
5.9.10.1、adapter模块的注册
(1)平台总线方式注册
(2)找到driver和device,并且确认其配对过程
(3)probe函数
5.9.10.2、probe函数分析
(1)填充一个i2c_adapter结构体,并且调用接口去注册之
(2)从platform_device接收硬件信息,做必要的处理(request_mem_region & ioremap、request_irq等)
(3)对硬件做初始化(直接操作210内部I2C控制器的寄存器)
5.9.10.3、i2c_algorithm
(1)i2c->adap.algo    = &s3c24xx_i2c_algorithm;
(2)functionality
(3)s3c24xx_i2c_doxfer5.9.11_12.linux内核的I2C子系统详解6_7
5.9.11.1、i2c_driver的注册
(1)以gslX680的驱动为例
(2)将驱动添加到内核SI项目中
(3)i2c_driver的基本分析:name和probe
5.9.11.2、i2c_client从哪里来
(1)直接来源:i2c_register_board_info
smdkc110_machine_initi2c_register_board_infostruct i2c_board_info {char     type[I2C_NAME_SIZE];            // 设备名unsigned short    flags;                      // 属性unsigned short addr;                       // 设备从地址void        *platform_data;                 // 设备私有数据struct dev_archdata    *archdata;
#ifdef CONFIG_OFstruct device_node *of_node;
#endifint       irq;                                // 设备使用的IRQ号,对应CPU的EINT
};(2)实现原理分析
内核维护一个链表 __i2c_board_list,这个链表上链接的是I2C总线上挂接的所有硬件设备的信息结构体。也就是说这个链表维护的是一个struct i2c_board_info结构体链表。
真正的需要的struct i2c_client在别的地方由__i2c_board_list链表中的各个节点内容来另外构建生成。函数调用层次:
i2c_add_adapter/i2c_add_numbered_adapteri2c_register_adapteri2c_scan_static_board_infoi2c_new_devicedevice_register总结:I2C总线的i2c_client的提供是内核通过i2c_add_adapter/i2c_add_numbered_adapter接口调用时自动生成的,生成的原料是mach-x210.c中的i2c_register_board_info(1, i2c_devs1, ARRAY_SIZE(i2c_devs1));5.9.13.gslX680驱动的移植实践
5.9.13.1、初步移植实验
(1)源码获取
(2)源码加入内核中
(3)mach文件中添加board_info
(4)编译后内核去启动
5.9.13.2、在内核配置中添加CONFIG项
(1)定义一个宏名,譬如CONFIG_TOUCHSCREEN_GSLX680
(2)在代码中使用宏来条件编译
(3)在Makefile中使用宏来条件配置
(4)在Kconfig项目中添加宏的配置项
(5)make menuconfig并选择Y或者N5.9.14_15.gslX680驱动源码分析1_25.9.16.老版本触摸屏的驱动
5.9.16.1、ft5x06驱动移植实践
5.9.16.2、ft5x06驱动源码分析

2、I2C子系统的4个关键结构体

(1)struct i2c_adapter            I2C适配器(i2c主机驱动)
(2)struct i2c_algorithm            I2C算法
(3)struct i2c_client            I2C(从机)设备信息
(4)struct i2c_driver            I2C(从机)设备驱动

在i2c_adapte结构体里面包含i2c_algorithm结构体,即每个适配器都要有一个对应的算法。

3、关键文件

(1)i2c-core.c            内核开发者实现的,与硬件无关的代码,主要为其它各部分提供操作接口,在其内部通过结构体里面的函数指针调用硬件相关信息,即结构体里面函数指针的函数在设备加载的时候初始化。
(2)busses目录         struct i2c_adapter实现相关的代码
(3)algos目录            struct i2c_algorithm相关,即一些要用到的算法,其实就是I2C的一些规则规范。

4、i2c-core.c初步分析

在老版本的内核里面在目录linux_kernel\linux-st\drivers\i2c下面是i2c-core.c,但是我这个4.19版本的内核目录下面没有i2c-core.c,这个文件被分成了几个文件


4.1、smbus代码略过

4.2、模块加载和卸载:bus_register(&i2c_bus_type);    在i2c-core-base.c中

static int __init i2c_init(void)
{int retval;retval = of_alias_get_highest_id("i2c");down_write(&__i2c_board_lock);if (retval >= __i2c_first_dynamic_bus_num)__i2c_first_dynamic_bus_num = retval + 1;up_write(&__i2c_board_lock);//这里注册了i2c以后,就可以在/sys/bus/目录下看到名为i2c的文件retval = bus_register(&i2c_bus_type); //注册总线,i2c_bus_type包含i2c总线的一些信息if (retval)return retval;is_registered = true;#ifdef CONFIG_I2C_COMPATi2c_adapter_compat_class = class_compat_register("i2c-adapter");if (!i2c_adapter_compat_class) {retval = -ENOMEM;goto bus_err;}
#endifretval = i2c_add_driver(&dummy_driver);//在目录/sys/bus/i2c/drivers添加了一个名为dummy的空驱动if (retval)goto class_err;if (IS_ENABLED(CONFIG_OF_DYNAMIC))WARN_ON(of_reconfig_notifier_register(&i2c_of_notifier));if (IS_ENABLED(CONFIG_ACPI))WARN_ON(acpi_reconfig_notifier_register(&i2c_acpi_notifier));return 0;class_err:
#ifdef CONFIG_I2C_COMPATclass_compat_unregister(i2c_adapter_compat_class);
bus_err:
#endifis_registered = false;bus_unregister(&i2c_bus_type);return retval;
}static void __exit i2c_exit(void)
{if (IS_ENABLED(CONFIG_ACPI))WARN_ON(acpi_reconfig_notifier_unregister(&i2c_acpi_notifier));if (IS_ENABLED(CONFIG_OF_DYNAMIC))WARN_ON(of_reconfig_notifier_unregister(&i2c_of_notifier));i2c_del_driver(&dummy_driver);
#ifdef CONFIG_I2C_COMPATclass_compat_unregister(i2c_adapter_compat_class);
#endifbus_unregister(&i2c_bus_type);tracepoint_synchronize_unregister();
}/* We must initialize early, because some subsystems register i2c drivers* in subsys_initcall() code, but are linked (and initialized) before i2c.*/
postcore_initcall(i2c_init);
module_exit(i2c_exit);

4.3、I2C总线的匹配机制


4.3.1、match函数

注意这里:设备名字只有一个,但是驱动却是一个id_table的数组,即驱动有多个名字,即一个驱动可能能适应多个设备,尤其是同一类型的设备。

4.3.2、probe函数

static int i2c_device_probe(struct device *dev)
{//这里i2c_client是设备,i2c_driver是驱动//当着两个都被注册到i2c总线上之后,由总线负责将这两个匹配并且调用驱动的probe函数struct i2c_client    *client = i2c_verify_client(dev);struct i2c_driver  *driver;int status;if (!client)return 0;driver = to_i2c_driver(dev->driver);if (!client->irq && !driver->disable_i2c_core_irq_mapping) {int irq = -ENOENT;if (client->flags & I2C_CLIENT_HOST_NOTIFY) {dev_dbg(dev, "Using Host Notify IRQ\n");/* Keep adapter active when Host Notify is required */pm_runtime_get_sync(&client->adapter->dev);irq = i2c_smbus_host_notify_to_irq(client);} else if (dev->of_node) {irq = of_irq_get_byname(dev->of_node, "irq");if (irq == -EINVAL || irq == -ENODATA)irq = of_irq_get(dev->of_node, 0);} else if (ACPI_COMPANION(dev)) {irq = acpi_dev_gpio_irq_get(ACPI_COMPANION(dev), 0);}if (irq == -EPROBE_DEFER)return irq;if (irq < 0)irq = 0;client->irq = irq;}/** An I2C ID table is not mandatory, if and only if, a suitable OF* or ACPI ID table is supplied for the probing device.*/if (!driver->id_table &&!i2c_acpi_match_device(dev->driver->acpi_match_table, client) &&!i2c_of_match_device(dev->driver->of_match_table, client))return -ENODEV;if (client->flags & I2C_CLIENT_WAKE) {int wakeirq = -ENOENT;if (dev->of_node) {wakeirq = of_irq_get_byname(dev->of_node, "wakeup");if (wakeirq == -EPROBE_DEFER)return wakeirq;}device_init_wakeup(&client->dev, true);if (wakeirq > 0 && wakeirq != client->irq)status = dev_pm_set_dedicated_wake_irq(dev, wakeirq);else if (client->irq > 0)status = dev_pm_set_wake_irq(dev, client->irq);elsestatus = 0;if (status)dev_warn(&client->dev, "failed to set up wakeup irq\n");}dev_dbg(dev, "probe\n");status = of_clk_set_defaults(dev->of_node, false);if (status < 0)goto err_clear_wakeup_irq;status = dev_pm_domain_attach(&client->dev, true);if (status)goto err_clear_wakeup_irq;/** When there are no more users of probe(),* rename probe_new to probe.*/if (driver->probe_new)status = driver->probe_new(client);else if (driver->probe)status = driver->probe(client,i2c_match_id(driver->id_table, client));//通过函数指针的方式来调用了一个函数,这个函数式driver里面的probe函数//所以说,当一个device和一个driver匹配上以后会去执行driver里面的probe,这就是为什么elsestatus = -EINVAL;if (status)goto err_detach_pm_domain;return 0;err_detach_pm_domain:dev_pm_domain_detach(&client->dev, true);
err_clear_wakeup_irq:dev_pm_clear_wake_irq(&client->dev);device_init_wakeup(&client->dev, false);return status;
}

总结:I2C总线上有2条分支:i2c_client链和i2c_driver链,当任何一个driver或者client去注册时,I2C总线都会调用match函数去对client.name和driver.id_table.name进行循环匹配。如果driver.id_table中所有的id都匹配不上则说明client并没有找到一个对应的driver,没了;如果匹配上了则标明client和driver是适用的,那么I2C总线会调用自身的probe函数,自身的probe函数又会调用driver中提供的probe函数,driver中的probe函数会对设备进行硬件初始化和后续工作。

4.4、核心层开放给其他部分的注册接口

(1)i2c_add_adapter/i2c_add_numbered_adapter        注册adapter的
(2)i2c_add_driver                                注册driver的
(3)i2c_new_device                                注册client的

5、adapter模块

5.1、adapter模块的注册
(1)平台总线方式注册
(2)找到driver和device,并且确认其配对过程
(3)probe函数
5.9.10.2、probe函数分析
(1)填充一个i2c_adapter结构体,并且调用接口去注册之
(2)从platform_device接收硬件信息,做必要的处理(request_mem_region & ioremap、request_irq等)
(3)对硬件做初始化(直接操作210内部I2C控制器的寄存器)

这里的adapter模块,朱老师的视频里面用的是三星写的一个说s3cs5pv210的驱动,内核也要老一些。但是我这个内核是4.19的,我的开发板是拜兔核的盘古开发板,用的MPU是stm32mp157.所以我就想找一下我系统下i2c驱动的具体实现在哪里?

先是在内核目录linux-st\drivers\i2c\busses下根据文件名查找一下:

大概看了一下这两个文件,感觉不太像,尤其是这个i2c-stm32f7.c文件,因为这个主控芯片是stm32mp157的,应该有这样一个文件才对啊。

然后从开发板上的文件系统里面看一下I2C的设备有哪些:

上图中的i2c-0和i2c-1应该是两个i2c控制器,结合设备树文件描述:

i2c5的设备树配置较长,如下,文件是stm32mp157a-panguboard.dts

&i2c5 {pinctrl-names = "default", "sleep";pinctrl-0 = <&i2c5_pins_a>;pinctrl-1 = <&i2c5_pins_sleep_a>;i2c-scl-rising-time-ns = <100>;i2c-scl-falling-time-ns = <7>;status = "okay";/delete-property/dmas;/delete-property/dma-names;cs42l51: cs42l51@4a {compatible = "cirrus,cs42l51";reg = <0x4a>;#sound-dai-cells = <0>;status = "okay";VL-supply = <&v3v3>;VD-supply = <&v1v8_audio>;VA-supply = <&v1v8_audio>;VAHP-supply = <&v1v8_audio>;reset-gpios = <&gpioc 0 GPIO_ACTIVE_LOW>;clocks = <&sai2a>;clock-names = "MCLK";cs42l51_port: port {#address-cells = <1>;#size-cells = <0>;cs42l51_tx_endpoint: endpoint@0 {reg = <0>;remote-endpoint = <&sai2a_endpoint>;frame-master;bitclock-master;};cs42l51_rx_endpoint: endpoint@1 {reg = <1>;remote-endpoint = <&sai2b_endpoint>;frame-master;bitclock-master;};};};hdmi: hdmi-transmitter@39 {compatible = "sil,sii9022";reg = <0x39>;iovcc-supply = <&v3v3_hdmi>;cvcc12-supply = <&v1v2_hdmi>;reset-gpios = <&gpioa 13 GPIO_ACTIVE_LOW>;interrupts = <14 IRQ_TYPE_EDGE_FALLING>;interrupt-parent = <&gpioa>;pinctrl-names = "default", "sleep";pinctrl-0 = <&ltdc_pins_b>;pinctrl-1 = <&ltdc_pins_sleep_b>;status = "okay";ports {#address-cells = <1>;#size-cells = <0>;port@0 {reg = <0>;sii9022_in: endpoint {remote-endpoint = <&ltdc_ep0_out>;};};port@1 {reg = <1>;sii9022_tx_endpoint: endpoint {remote-endpoint = <&i2s2_endpoint>;};};};};ov5640: camera@3c {compatible = "ovti,ov5640";reg = <0x3c>;clocks = <&clk_ext_camera>;clock-names = "xclk";DOVDD-supply = <&v2v8>;powerdown-gpios = <&gpioa 4 GPIO_ACTIVE_HIGH>;reset-gpios = <&gpioa 3 GPIO_ACTIVE_LOW>;rotation = <180>;status = "okay";port {ov5640_0: endpoint {remote-endpoint = <&dcmi_0>;bus-width = <8>;data-shift = <2>; /* lines 9:2 are used */hsync-active = <0>;vsync-active = <0>;pclk-sample = <1>;pclk-max-frequency = <77000000>;};};};
};

以上,结合文件系统中设备名字和设备树中的配置信息可以得出:

以上设备树配置方式可以看出来,是先配置i2c设备(i2c总线),那些挂在该总线上的设备(如触摸屏、摄像头等等)都是i2c的一个子节点。那么内核解析设备树的时候,肯定是先加载i2c总线,然后再加载该总线上挂的设备。分析到这里,突然发现应该就是上面提到的那个看着不像的文件i2c-stm32f7.c(目录linux-st\drivers\i2c\busses)里面实现的i2c总线的具体驱动。

i2c-stm32f7.c(目录linux-st\drivers\i2c\busses)这个文件里面:

5.1、adapter模块的注册

5.1.1、平台总线方式注册


5.1.2、找到driver和device,并且确认其配对过程

5.2、probe函数

(1)填充一个i2c_adapter结构体,并且调用接口去注册之


(2)从platform_device接收硬件信息,做必要的处理(request_mem_region & ioremap、request_irq等)
(3)对硬件做初始化(直接操作210内部I2C控制器的寄存器)

5.9.10.3、i2c_algorithm
(1)i2c->adap.algo    = &s3c24xx_i2c_algorithm;
(2)functionality
(3)s3c24xx_i2c_doxfer

这一块在我的代码中没有找到与之对应的部分

6、i2c_driver的注册

(1)以gslX680的驱动为例
(2)将驱动添加到内核SI项目中

在我的系统中使用edt-ft5x06.c(目录:linux-st\drivers\input\touchscreen)来分析,这是盘古开发板上带的触摸屏驱动

上图是新内核和老内核在i2c driver注册时不太一样的地方,但是原理是一样的。这就是i2c_driver的注册
(3)i2c_driver的基本分析:name和probe

7、i2c_client从哪里来

因为上面i2c_driver的注册的时候,它的probe函数有一个入口参数是i2c_client,所以需要看一下这个i2c_client是从哪里来的

(1)直接来源:i2c_register_board_info
smdkc110_machine_init
    i2c_register_board_info
    
struct i2c_board_info {
    char        type[I2C_NAME_SIZE];            // 设备名
    unsigned short    flags;                        // 属性
    unsigned short    addr;                        // 设备从地址
    void        *platform_data;                    // 设备私有数据
    struct dev_archdata    *archdata;
#ifdef CONFIG_OF
    struct device_node *of_node;
#endif
    int        irq;                                // 设备使用的IRQ号,对应CPU的EINT
};

(2)实现原理分析
内核维护一个链表 __i2c_board_list,这个链表上链接的是I2C总线上挂接的所有硬件设备的信息结构体。也就是说这个链表维护的是一个struct i2c_board_info结构体链表。
真正的需要的struct i2c_client在别的地方由__i2c_board_list链表中的各个节点内容来另外构建生成。

函数调用层次:
i2c_add_adapter/i2c_add_numbered_adapter
    i2c_register_adapter
        i2c_scan_static_board_info
            i2c_new_device
                device_register

总结:I2C总线的i2c_client的提供是内核通过i2c_add_adapter/i2c_add_numbered_adapter接口调用时自动生成的,生成的原料是mach-x210.c中的i2c_register_board_info(1, i2c_devs1, ARRAY_SIZE(i2c_devs1));

以上是朱老师视频里面的注册流程,他用的内核是没有设备树的。我用的内核版本是4.19,是带有设备树的版本,那么带设备树版本的内核中i2c_client从哪里来呢?

看上图中这个i2c_register_driver函数的注释,大概可以得出是在这里面去匹配设备(i2c_client)的。

第一种情况是这一句:driver->driver.bus = &i2c_bus_type;这里将i2c_bus_type的地址赋给了driver->driver.bus

再往后就和https://blog.csdn.net/fang_yang_wa/article/details/71629666里面总线设备和驱动匹配一样了。

第二种情况是这里:

/* Walk the adapters that are already present */
i2c_for_each_dev(driver, __process_new_driver);

应该是这两种情况下找到了i2c_client,并且与之做了匹配,然后执行了驱动的probe函数,值得注意的是:设备和驱动的匹配是由框架和总线完成的,并不是在设备和驱动本身实现的。

这两种情况目前我还不是太清楚具体的工作,因为想触摸屏设备在设备树里面是i2c设备的一个子节点。比如i2c2上面有多个设备,i2c5上面也有多个设备,那么这里匹配的时候应该是在所属的总线上去做匹配吧?也不一定?

还有一种情况是:

i2c_verify_client—返回参数i2c_client,或NULL dev: device,可能在遍历驱动模型树时从某个驱动模型迭代器返回,可能使用驱动模型迭代器如@device_for_each_child(),您不能对所找到的节点进行太多假设。

使用这个函数可以避免错误地将一些非i2c设备当作i2c_client而导致的oopses。

官方:

Description

Systems using the Linux I2C driver stack can declare tables of board info while they initialize. This should be done in board-specific init code near arch_initcall() time, or equivalent, before any I2C adapter driver is registered. For example, mainboard init code could define several devices, as could the init code for each daughtercard in a board stack.

The I2C devices will be created later, after the adapter for the relevant bus has been registered. After that moment, standard driver model tools are used to bind “new style” I2C drivers to the devices. The bus number for any device declared using this routine is not available for dynamic allocation.

The board info passed can safely be __initdata, but be careful of embedded pointers (for platform_data, functions, etc) since that won’t be copied. Device properties are deep-copied though.

struct i2c_client * i2c_verify_client(struct device * dev)

return parameter as i2c_client, or NULL

Parameters

struct device * dev

device, probably from some driver model iterator

给一个device检查是否有对应的client

这个i2c_client的来源就先这样了。。。后面用到了再深入。

这个i2c_client的来源在这几篇博客上找到了答案:

https://www.cnblogs.com/multimicro/p/11905647.html

https://blog.csdn.net/multimicro/article/details/103129546

https://github.com/wifialan/drivers/blob/master/device_tree_i2c/device_tree_node_transfer

第05节_device_node转换为platform_devicedts -> dtb -> device_node -> platform_device两个问题:
a. 哪些device_node可以转换为platform_device?
根节点下含有compatile属性的子节点
如果一个结点的compatile属性含有这些特殊的值("simple-bus","simple-mfd","isa","arm,amba-bus")之一, 那么它的子结点(需含compatile属性)也可以转换为platform_device
i2c, spi等总线节点下的子节点, 应该交给对应的总线驱动程序来处理, 它们不应该被转换为platform_deviceb. 怎么转换?
platform_device中含有resource数组, 它来自device_node的reg, interrupts属性;
platform_device.dev.of_node指向device_node, 可以通过它获得其他属性本节总结:a. 内核函数of_platform_default_populate_init, 遍历device_node树, 生成platform_device
b. 并非所有的device_node都会转换为platform_device只有以下的device_node会转换:
b.1 该节点必须含有compatible属性
b.2 根节点的子节点(节点必须含有compatible属性)
b.3 含有特殊compatible属性的节点的子节点(子节点必须含有compatible属性):这些特殊的compatilbe属性为: "simple-bus","simple-mfd","isa","arm,amba-bus"b.4 示例: 比如以下的节点, /mytest会被转换为platform_device, 因为它兼容"simple-bus", 它的子节点/mytest/mytest@0 也会被转换为platform_device/i2c节点一般表示i2c控制器, 它会被转换为platform_device, 在内核中有对应的platform_driver;/i2c/at24c02节点不会被转换为platform_device, 它被如何处理完全由父节点的platform_driver决定, 一般是被创建为一个i2c_client。类似的也有/spi节点, 它一般也是用来表示SPI控制器, 它会被转换为platform_device, 在内核中有对应的platform_driver;/spi/flash@0节点不会被转换为platform_device, 它被如何处理完全由父节点的platform_driver决定, 一般是被创建为一个spi_device。/ {mytest {compatile = "mytest", "simple-bus";mytest@0 {compatile = "mytest_0";};};i2c {compatile = "samsung,i2c";at24c02 {compatile = "at24c02";                      };};spi {compatile = "samsung,spi";              flash@0 {compatible = "winbond,w25q32dw";spi-max-frequency = <25000000>;reg = <0>;};};};函数调用过程:
a. of_platform_default_populate_init (drivers/of/platform.c) 被调用到过程:
start_kernel     // init/main.crest_init();pid = kernel_thread(kernel_init, NULL, CLONE_FS);kernel_initkernel_init_freeable();do_basic_setup();do_initcalls();for (level = 0; level < ARRAY_SIZE(initcall_levels) - 1; level++)do_initcall_level(level);  // 比如 do_initcall_level(3)for (fn = initcall_levels[3]; fn < initcall_levels[3+1]; fn++)do_one_initcall(initcall_from_entry(fn));  // 就是调用"arch_initcall_sync(fn)"中定义的fn函数b. of_platform_default_populate_init  (drivers/of/platform.c) 生成platform_device的过程:
of_platform_default_populate_initof_platform_default_populate(NULL, NULL, NULL);of_platform_populate(NULL, of_default_bus_match_table, NULL, NULL)for_each_child_of_node(root, child) {rc = of_platform_bus_create(child, matches, lookup, parent, true);  // 调用过程看下面dev = of_device_alloc(np, bus_id, parent);   // 根据device_node节点的属性设置platform_device的resourceif (rc) {of_node_put(child);break;}}c. of_platform_bus_create(bus, matches, ...)的调用过程(处理bus节点生成platform_devie, 并决定是否处理它的子节点):dev = of_platform_device_create_pdata(bus, bus_id, platform_data, parent);  // 生成bus节点的platform_device结构体if (!dev || !of_match_node(matches, bus))  // 如果bus节点的compatile属性不吻合matches成表, 就不处理它的子节点return 0;for_each_child_of_node(bus, child) {    // 取出每一个子节点pr_debug("   create child: %pOF\n", child);rc = of_platform_bus_create(child, matches, lookup, &dev->dev, strict);   // 处理它的子节点, of_platform_bus_create是一个递归调用if (rc) {of_node_put(child);break;}}d. I2C总线节点的处理过程:/i2c节点一般表示i2c控制器, 它会被转换为platform_device, 在内核中有对应的platform_driver;platform_driver的probe函数中会调用i2c_add_numbered_adapter:i2c_add_numbered_adapter   // drivers/i2c/i2c-core-base.c__i2c_add_numbered_adapteri2c_register_adapterof_i2c_register_devices(adap);   // drivers/i2c/i2c-core-of.cfor_each_available_child_of_node(bus, node) {client = of_i2c_register_device(adap, node);client = i2c_new_device(adap, &info);   // 设备树中的i2c子节点被转换为i2c_client}e. SPI总线节点的处理过程:/spi节点一般表示spi控制器, 它会被转换为platform_device, 在内核中有对应的platform_driver;platform_driver的probe函数中会调用spi_register_master, 即spi_register_controller:spi_register_controller        // drivers/spi/spi.cof_register_spi_devices   // drivers/spi/spi.cfor_each_available_child_of_node(ctlr->dev.of_node, nc) {spi = of_register_spi_device(ctlr, nc);  // 设备树中的spi子节点被转换为spi_devicespi = spi_alloc_device(ctlr);rc = of_spi_parse_dt(ctlr, spi, nc);rc = spi_add_device(spi);}

Linux驱动分析——I2C子系统相关推荐

  1. linux驱动之I2C子系统

    目录 一.I2C基本原理 二.linux内核的I2C子系统详解 1.linux内核的I2C驱动框架总览 2.I2C子系统的4个关键结构体(kernel/include/linux/i2c.h) 3.关 ...

  2. 【Linux驱动】I2C子系统与触摸屏驱动

    由于学习触摸屏驱动涉及到中断以及I2C相关的知识,因此先介绍一下I2C的驱动框架. 触摸屏与I2C总线的关系 关于I2C的基础概念和原理参考我的这篇博客:[裸机]嵌入式相关问题汇总(二.I2C通信概念 ...

  3. linux驱动之i2c子系统mpu6050设备驱动

    以下是mpu6050简单的驱动实现,mpu6050是I2C接口的6轴传感器,可以作为字符设备注册到内核,本代码运行环境是3.4.2内核,4.3.2版本的编译链,12.04版本的Ubuntu,硬件环境是 ...

  4. linux i2c子系统看不懂啊,Linux 下的I2C子系统

    Linux 下的I2C子系统 2013.7.16 本文分为两部分,一.设备模型 二.平台相关 . ================================================ 第一 ...

  5. 【linux驱动分析】之dm9000驱动分析

    [linux驱动分析]之dm9000驱动分析(一):dm9000原理及硬件分析 [linux驱动分析]之dm9000驱动分析(一):dm9000原理及硬件分析 [linux驱动分析]之dm9000驱动 ...

  6. 【linux驱动分析】之dm9000驱动分析(三):sk_buff结构分析

    [linux驱动分析]之dm9000驱动分析(一):dm9000原理及硬件分析 [linux驱动分析]之dm9000驱动分析(二):定义在板文件里的资源和设备以及几个宏 [linux驱动分析]之dm9 ...

  7. 千兆网口 Freescale ETSEC + Marvell 88E1111 uboot Linux 驱动分析

    原文  http://blog.csdn.net/gorilla0123/article/details/5972706 千兆网口 Freescale ETSEC + Marvell 88E1111 ...

  8. CMOS摄像头驱动分析-i2c驱动

    CMOS摄像头驱动分析-i2c驱动 文章目录 CMOS摄像头驱动分析-i2c驱动 设备树内容 module_i2c_driver宏分析 ov2640_i2c_driver ov2640_probe 设 ...

  9. 【linux驱动】USB子系统分析

    本文针对Linux内核下USB子系统进行分析,主要会涉及一下几个方面: USB基础知识:介绍USB设备相关的基础知识 Linux USB子系统分析:分析USB系统框架,USB HCD/ROOT HUB ...

最新文章

  1. 万物生长,万物互联的时代来了
  2. 电脑尺寸大小在哪里看_科技资讯:电脑弹出本地计算机上的服务启动后停止的提示在哪里看...
  3. java里加载是什么意思_Java 类加载机制详解
  4. 【报告分享】2020年创新报告-埃森哲.pdf(附下载链接)
  5. OpenCV-寻找轮廓cv::findContours绘制轮廓cv::drawContours
  6. 软考中级职称 软件工程师 学习知识点记录
  7. Sass 基础教程——基本介绍
  8. ATtiny85 制作迷你小游戏机
  9. 在线作图工具:ProcessOn,流程图-思维导图-原型图-UML图等
  10. php fatal 和php error,从PHP Fatal error: Uncaught Error: Class '' not found in php:说起
  11. 用 Python 帮女友修改微信运动步数,妹子直呼内行
  12. OPPO R7Plusm(全网通)root、刷入twrp recovery、卡刷刷入CM系统教程合集_ recovery.img文件下载 联想A7600-m线刷刷机教程 手机卡在双4G双百兆无法开
  13. hive中时间类date函数
  14. 2023年全国管理类联考英语二真题及解析
  15. Java GUI编写一个简单的抽奖机
  16. 数学上的表示“任意”和“存在”的符号
  17. Tool:Visio2016/Visio2019专业版64位中文下载、安装(图文教程)之详细攻略
  18. STM32 SPI片选信号拉不高的解决方案
  19. C语言为什么不会过时
  20. Boundary Smoothing for NER

热门文章

  1. 什么是HTML超链接?锚链接?
  2. 数码相机里的光学变焦和数字变焦的区别
  3. 数据分析可视化04 图表组件:Echarts数据可视化图表基础
  4. BIND子域授权及正向解析实现
  5. 打造自己的辅助工具,提高开发效率
  6. H.266/VVC熵编码之二进制化
  7. jsp70877婚庆策划婚车预订网站
  8. 均值漂移(mean shift )聚类算法Matlab实现详解
  9. 国际化域名 idn linux,CLUB中文国际化域名(IDN)9月开放注册
  10. git push--解决 /etc/ssh/ssh_config: Bad configuration option: permitrootlogin 问题