https://www.yuque.com/u2132176/yfiyal/ch1zsrgzevcwf1rw
视频教程里面对应的gc2053c驱动源码注解:
gc2053.c(60 KB)
对应的驱动文档:
Rockchip_Driver_Guide_VI_CN_v1.1.1(2).pdf(2.3 MB)
视频里面对应的mipi协议文档汇总:
MIPI标准文档大全.rar_mipi协议资源.rar(28.8 MB)
视频对应链接:
第九期: https://www.bilibili.com/video/BV11r421877V/
一、第8期内容讲解:
1、了解mipi、uvc、dvp接口:
- USB:是串行通用串行总线(Universal Serial Bus)的简称
- MIPI是移动行业处理器接口(Mobile Industry Processor Interface)
mipi接口的特点:
MIPI是差分串口传输,速度快,抗干扰。主流手机模组现在都是用MIPI传输,传输时使用4对差分信号传输图像数据和一对差分时钟信号;
最初是为了减少LCD屏和主控芯片之间连线的数量而设计的,后来发展到高速了,支持高分辨率的显示屏,现在基本上都是MIPI接口了。
MIPI摄像头有三个电源:VDDIO(IO电源),AVDD(模拟电源),DVDD(内核数字电源),不同sensor模组的摄像头供电不同,
AVDD有2.8V或3.3V的;DVDD一般使用1.5V或更高,不同厂家的设计不同,1.5V可能由sensor模组提供或外部供给,
可以使用外部供电则建议使用外部供,电压需大于内部的DVDD;VDDIO电压应与MIPI信号线的电平一致,
若信号线是2.8V电平,则VDDIO也应供2.8V,有些sensor模组也可以不供VDDIO,由内部提供。
- DVP是数字视频端口(digital video port)的简称
dvp接口特点:
DVP总线PCLK极限约在96M左右,而且走线长度不能过长,所有DVP最大速率最好控制在72M以下,PCB layout较容易画;
MIPI总线速率lvds接口耦合,走线必须差分等长,并且需要保护,
故对PCB走线以及阻抗控制要求高一点(一般来讲差分阻抗要求在85欧姆~125欧姆之间)。
DVP是并口,需要PCLK、VSYNC、HSYNC、D[0:11]——可以是8/10/12bit数据,具体情况要看ISP或baseband是否支持;
MIPI是LVDS低压差分串口,只需要要CLKP/N、DATAP/N——最大支持4-lane,一般2-lane可以搞定。MIPI接口比DVP的接口信号线少,
由于是低压差分信号,产生的干扰小,抗干扰能力也强。最重要的是DVP接口在信号完整性方面受限制,速率也受限制。
500W还可以勉强用DVP,800W及以上都采用MIPI接口。
- CSI是相机串行接口(CMOS Sensor Interface)的简称
价格因素汇总:MIPI 摄像头和 DVP 摄像头价格,从接口上分不出高低。比如 OV5640 它同时支持两种接口,主要还是像素尺寸、
像素点的个数有区别。此外,一些高分辨率摄像头只支持 MIPI,一些低分辨率摄像头只支持 SPI\DVP 接口。
同等的 USB 摄像头会略微贵一些。
开机速度:通常 MIPI\DVP 开机启动速度快,USB 开机启动速度慢。
数据格式:原生的 MIPI\DVP 摄像头可以直接通过 DMA,然后可以传递给 ISP\SoC,可以拿到 raw 数据。
低功耗:MIPI 的耗电可以非常低,usb 耗电较高,并口的 DVP接口 功耗最高。
开发成本:MIPI\DVP 的摄像头依赖额外的驱动程序来工作。这意味着对不同图像传感器的支持有限,
除非有专门的摄像头厂商或开发人员真正推动它!而 USB 摄像头对用户而言是免驱的。
总结
1)USB、DVP、MIPI 都可以对接摄像头,他们的特点不同,其中 USB 是异步串行接口、MIPI 是同步串行接口、DVP是同步并行串口。
2)不同摄像头方案对摄像头的需求不同,因此在考虑抗干扰能力、分辨率大小、开发成本、设备体积的基础上对摄像头接口进行选型,进而选择合适的摄像头是非常重要的。
v4l2驱动框架,简介:
几乎所有的设备都有多个 IC 模块,它们可能是实体的(例如 USB 摄像头里面包含 ISP、sensor 等)、也可能是抽象的(如 USB 设备里面的抽象拓扑结构),它们在 /dev 目录下面生成了多个设备节点,并且这些 IC 模块还创建了一些非 v4l2 设备:DVB、ALSA、FB、I2C 和输入设备。正是由于硬件的复杂性,v4l2 的驱动也变得非常复杂。特别是 v4l2 驱动要支持 IC 模块来进行音/视频的混合/编解码操作,这就更加使得 v4l2 驱动变得异常复杂。通常情况下,有些IC模块通过一个或者多个 I2C 总线连接到主桥驱动上面,同时其它的总线仍然可用,这些 IC 就称为 ‘sub-devices’,比如摄像头设备里面的 sensor 传感器就是使用 I2C 来进行命令沟通。
同时使用 MIPI 或者 LVDS 等接口进行图像数据传输。在很长一段时间内,该框架(指老旧的 V4L2 框架)仅限于通过 video_device 结构体创建 v4l 设备节点和 video_buf 来处理视频数据。这意味着所有的驱动都必须对设备实例进行设置并将其映射到子设备上。有些时候这些操作步骤十分复杂,很难正确完成,并且有些驱动程序从来没有正确的按照这些操作步骤编写。由于缺少一个框架,有很多通用代码就没有办法被重构,从而导致这部分代码被重复编写,效率比较低下。
因此,本框架抽象构建了所有驱动都需要的代码并封装为一个个的模块,
简化了设备驱动通用代码的重构。v4l2-pci-skeleton.c 是个非常好的参考例程,它是一个PCI采集卡的驱动框架。该例程演示了如何使用 v4l2 驱动框架,并且该例程可以作为一个 PCI 视频采集卡的驱动模板使用。
在最开始的时候也可以参照这个代码编写方式进行联系,当然最适合的代码还是 drivers/media/video/omap3isp 文件夹里面的代码,
这个代码基本上可以作为一个完整的输入设备实例代码(因为它包含了 ISP、CSI、video 等设备,并且有着一个完整的数据流 pipeline,
几乎用到了 V4L2 框架的方方面面,参考价值极大)来进行参考编写自己的设备驱动代码。
v4l2_device:这个是整个输入设备的总结构体,可以认为它是整个 V4L2 框架的入口,充当驱动的管理者以及入口监护人。由该结构体引申出来 v4l2_subdev。用于视频输入设备整体的管理,有多少输入设备就有多少个v4l2_device抽象(比如一个USB摄像头整体就可以看作是一个 V4L2 device)。再往下分是输入子设备,对应的是例如 ISP、CSI、MIPI 等设备,它们是从属于一个 V4L2 device 之下的。
media_device:用于运行时数据流的管理,嵌入在 V4L2 device 内部,
运行时的意思就是:一个 V4L2 device 下属可能有非常多同类型的子设备(两个或者多个 sensor、ISP 等),那么在设备运行的时候我怎么知道我的数据流需要用到哪一个类型的哪一个子设备呢。这个时候就轮到 media_device 出手了,它为这一坨的子设备建立一条虚拟的连线,建立起来一个运行时的 pipeline(管道),并且可以在运行时动态改变、管理接入的设备。
v4l2_ctrl_handler:控制模块,提供子设备(主要是 video 和 ISP 设备)在用户空间的特效操作接口,比如你想改变下输出图像的亮度、对比度、饱和度等等,都可以通过这个来完成。
vb2_queue:提供内核与用户空间的 buffer 流转接口,输入设备产生了一坨图像数据,在内核里面应该放在哪里呢?能放几个呢?是整段连续的还是还是分段连续的又或者是物理不连续的?用户怎么去取用呢?都是它在管理。
可以看到图中的入口 custom_v4l2_dev,它是由用户定义的一个结构体,重要的不是它怎么定义的,重要的是它里面有一个 v4l2_device 结构体,上文说到,这个结构体总览全局,运筹帷幄,相当于中央管理处的位置,那么中央决定了,
它就是整个输入设备整体的抽象(比如整个 USB 摄像头输入设备,比如整个 IPC 摄像头输入设备)。它还有一个 media_device 结构体,上文也说道,,它是管数据流线路的,属于搞结构路线规划管理的。
往后 v4l2_device 里面有一个链表,它维护了一个巨大的子设备链,所有的子设备都通过内核的双向循环链表结构以v4l2_device 为中心紧紧团结在一起。另外 media_device 在往里面去就是一个个的 media_entity
(现在不需要了解它的具体含义,只需要知道它就是类似电路板上面的元器件一样的抽象体),media_entity 之间建立了自己的小圈子,在它们这个小圈子里面数据流按照一定的顺序畅通无阻,恣意遨游。
到结尾处,抽象出来了 /dev/videoX 设备节点,这个就是外交部的角色,它负责提供了一个内核与用户空间的交流枢纽。
需要注意的是,该设备节点的本质还是一个字符设备,其内部的一套操作与字符设备是一样的,只不过是进行了一层封装而已。
到此为止,一个 V4L2 大概的四层结构就抽象出来了,如下图所示:
media framework 其中一个目的是:在运行时状态下发现设备拓扑并对其进行配置。为了达到这个目的,media framework 将硬件设备抽象为一个个 entity,它们之间通过 links 连接。
media_device,media_entity,media_link,media_pad
1、entity:硬件设备模块抽象(类比电路板上面的各个元器件、芯片)
2、pad:硬件设备端口抽象(类比元器件、芯片上面的管脚)
3、link:硬件设备的连接抽象,link 的两端是 pad(类比元器件管脚之间的连线)
第9期内容:
问题交流和我个人微信: 13717133207
1、本期内容结束sensor驱动源码的讲解
2、介绍一下v4l2的学习和v4l2_subdev的介绍
3、梳理整个sensor驱动源码主要做了什么
4、第10期开始讲解mipi传输,怎么把sensor的数据传输到Isp那边去做处理
1、v4l2_subdev的详细介绍:
很多设备都需要与子设备进行交互,通常情况下子设备用于音视频的编解码以及混合处理,对于网络摄像机来说子设备就是 sensors 和 camera 控制器。通常情况下它们都是 I2C 设备,但也有例外。v4l2_subdev 结构体被用于子设备管理。
每一个子设备驱动都必须有一个 v4l2_subdev 结构体,这个结构体可以作为独立的简单子设备存在,也可以嵌入到更大的结构体(自定义的子设备结构体)里面。通常会有一个由内核设置的低层次结构体(i2c_client,也就是上面说的 i2c 设备),它包含了一些设备数据,要调用 v4l2_set_subdevdata 来设置子设备私有数据指针指向它,这样的话就可以很方便的从 subdev 找到相关的 I2C 设备数据(这个要编程实现的时候才能够了解它的用意)。另外也需要设置低级别结构的私有数据指针指向 v4l2_subdev 结构体,方便从低级别的结构体访问 v4l2_subdev 结构体,达到双向访问的目的,对于 i2c_client 来说,可以用 i2c_set_clientdata 函数来设置,其它的需使用与之相应的函数来完成设置。
桥驱动器需要存储每一个子设备的私有数据,v4l2_subdev 结构体提供了主机私有数据指针成员来实现此目的,使用以下函数可以对主机私有数据进行访问控制:
v4l2_get_subdev_hostdata();
v4l2_set_subdev_hostdata();
从桥驱动器的角度来看,我们加载子设备模块之后可以用某种方式获取子设备指针。对于 i2c 设备来说,调用 i2c_get_clientdata 函数即可完成,其它类型的设备也有与之相似的操作,在内核里面提供了不少的帮助函数来协助完成这部分工作,编程时可以多多使用。
每个 v4l2_subdev 结构体都包含有一些函数指针,指向驱动实现的回调函数,内核对这些回调函数进行了分类以避免出现定义了一个巨大的回调函数集,但是里面只有那么几个用得上的尴尬情况。最顶层的操作函数结构体内部包含指向各个不同类别操作函数结构体的指针成员,如下所示:
struct v4l2_subdev_core_ops {int (*log_status)(struct v4l2_subdev *sd);int (*s_io_pin_config)(struct v4l2_subdev *sd, size_t n,struct v4l2_subdev_io_pin_config *pincfg);int (*init)(struct v4l2_subdev *sd, u32 val);int (*load_fw)(struct v4l2_subdev *sd);int (*reset)(struct v4l2_subdev *sd, u32 val);int (*s_gpio)(struct v4l2_subdev *sd, u32 val);long (*ioctl)(struct v4l2_subdev *sd, unsigned int cmd, void *arg);
#ifdef CONFIG_COMPATlong (*compat_ioctl32)(struct v4l2_subdev *sd, unsigned int cmd,unsigned long arg);
#endif
#ifdef CONFIG_VIDEO_ADV_DEBUGint (*g_register)(struct v4l2_subdev *sd, struct v4l2_dbg_register *reg);int (*s_register)(struct v4l2_subdev *sd, const struct v4l2_dbg_register *reg);
#endifint (*s_power)(struct v4l2_subdev *sd, int on);int (*interrupt_service_routine)(struct v4l2_subdev *sd,u32 status, bool *handled);int (*subscribe_event)(struct v4l2_subdev *sd, struct v4l2_fh *fh,struct v4l2_event_subscription *sub);int (*unsubscribe_event)(struct v4l2_subdev *sd, struct v4l2_fh *fh,struct v4l2_event_subscription *sub);
};
struct v4l2_subdev {
#if defined(CONFIG_MEDIA_CONTROLLER)struct media_entity entity;
#endifstruct list_head list;struct module *owner;bool owner_v4l2_dev;u32 flags;struct v4l2_device *v4l2_dev;const struct v4l2_subdev_ops *ops;const struct v4l2_subdev_internal_ops *internal_ops;struct v4l2_ctrl_handler *ctrl_handler;char name[V4L2_SUBDEV_NAME_SIZE];u32 grp_id;void *dev_priv;void *host_priv;struct video_device *devnode;struct device *dev;struct fwnode_handle *fwnode;struct list_head async_list;struct v4l2_async_subdev *asd;struct v4l2_async_notifier *notifier;struct v4l2_async_notifier *subdev_notifier;struct v4l2_subdev_platform_data *pdata;
};
子设备驱动的初始化使用 v4l2_subdev_init 函数来完成(该函数只是初始化一些 v4l2_subdev 的成员变量,内容比较简单),在初始化之后需要设置子设备结构体的 name 和 owner 成员(如果是 i2c 设备的话,这个在 i2c helper 函数里面就会被设置).该部分 ioctl 可以直接通过用户空间的 ioctl 命令访问到(前提是该子设备在用户空间生成了子设备节点,这样的话就可以操作子设备节点来进行 ioctl)。内核里面可以使用 v4l2_subdev_call 函数来对这些回调函数进行调用,这个在 pipeline 管理的时候十分受用。
下面是gc2053里面的源码分析:
v4l2_i2c_subdev_init接口的说明:
v4l2_i2c_subdev_init : 这是一个函数调用,用于初始化一个v4l2_subdev结构,该结构表示一个V4L2视频子设备。
sd : 这是一个指向v4l2_subdev结构的指针,表示要初始化的视频子设备。
client : 这是一个指向i2c_client结构的指针,包含有关I2C设备的信息,如地址、总线等。
&gc2053_subdev_ops : 这是一个指向操作函数集的指针,用于指定视频子设备的操作函数,这些函数包括控制、配置等。
这段代码的作用是使用给定的i2c_client结构和操作函数集来初始化一个v4l2_subdev结构,从而创建一个表示特定I2C视频子设备的数据结构。 也就是说在Linux V4L2框架下,初始化一个型号为gc2053的I2C子设备,并为其注册一系列的操作函数,以便后续可以通过这些函数与子设备进行交互。
第10期内容讲解:
本期内容开始,我们开始新的内容学习,也就是开始讲解mipi_d-phy驱动源码学习;
有问题可以加v进行交流(需要系统学习嵌入式音视频项目的付费也可加):13717133207
首先mipi-dpy的驱动源码,在课程sdk里面的Linux kernel路径下,如下图所示:
首先在讲解mipi的物理层驱动代码讲解之前,非常有必要详细的来看一下mipi物理层的学习,这块我是参考原文档来学习汇总的(版本不一样,内容稍微也会不同,但是大体是一样的):
MIPI_D-PHY_specification_v1-2.pdf(2.3 MB)
sensor的视频流整体路线:
gc2053 csi_dphy0 mipi_csi2 rkcif_mipi_lvdsucam_out0 ---> csi_dphy0_input -- csi_dphy0_output --> mipi_csi2_input -- mipi_csi2_output <---> cif_mipi_in
对应的设备树:
&csi_dphy0 {status = "okay";
//mipi有两端一段连接Sensor,另一端连接 mipi_csi2_inputports {#address-cells = <1>;#size-cells = <0>;//这里为啥有两个端口呢,第一个端口是作为sensor的输入port@0 {reg = <0>;#address-cells = <1>;#size-cells = <0>;mipi_in_ucam0: endpoint@1 {reg = <1>;remote-endpoint = <&ucam_out0>;
// data-lanes = <1 2 3 4>;data-lanes = <1 2>; // only for GC2053};};//这个端口是输出给mipi_csi2port@1 {reg = <1>;#address-cells = <1>;#size-cells = <0>;csidphy0_out: endpoint@0 {reg = <0>;//mipi_csi2的port名称remote-endpoint = <&mipi_csi2_input>;};};};
};
来看一下mipi_d-phy的大纲介绍:
D-PHY在主设备和从设备之间提供了一个同步连接。一个实际的PHY配置包括一个时钟信号和一个或多个数据信号。时钟信号是单向的,起源于主设备并终止于从设备。数据信号可以是单向的或双向的,这取决于所选的选项。对于半双工操作,反向带宽是正向带宽的四分之一。使用令牌传递来控制链路的通信方向。(白话来说:D-PHY是一种高速串行接口,它通过同步连接实现主从设备之间的通信。在这种配置中,时钟信号负责同步,它从主设备发出并传输到从设备。数据信号则负责传输数据,其传输方向可以是单向或双向,具体取决于系统的设计需求。 在半双工模式下,D-PHY的通信是单向的,即在任何给定时刻,数据只能在一个方向上传输。这意味着反向带宽(从从设备到主设备的带宽)是正向带宽(从主设备到从设备的带宽)的四分之一。为了在主设备和从设备之间切换通信方向,D-PHY采用了令牌传递机制,这是一种确保在特定时间内只有一个设备在发送数据的协议,从而避免了数据冲突并提高了通信效率)。
链路包括一个高速信号模式,用于快速数据传输,以及一个低功耗信号模式,用于控制目的。可选地,可以使用低功耗逸出模式进行低速异步数据通信。高速数据通信以突发方式出现,具有任意数量的有效数据字节。
PHY为每个数据通道使用两根导线,另外为时钟通道使用两根导线。这构成了最小PHY配置所需的四根导线。在高速模式下,每个通道在两端都被终结,并且由一个低摆幅的差分信号驱动。在低功耗模式下,所有导线都是单端操作且未终结的。出于电磁干扰(EMI)的考虑,该模式下的驱动器应该是速率控制和电流限制的
(1)mipi_d-phy的整体架构:
Lane Module: 数据传输通道的基本单元
HS-TX :High-Speed Transmitter (Low-Swing Differential)
HS-RS: High-Speed Transmitter(Low-Swing Differential)
LP: Low-Power :identifier for opteration mode
LP-CD: Low-Power Contention Dectector
LP-RX : Low-Power Receiver (Large-Swing Single-Ended)
LP-TX: Low-Power Transmitter (Large-Swing Single-Ended)
高速收发器(HS-TX/RX):在高速模式下,负责发送和接收差分信号。
低功耗收发器(LP-TX/RX):在低功耗模式下,负责发送和接收单端信号。
低功耗竞争检测器(LP-CD):用于检测低功耗模式下的信号冲突。
控制逻辑:管理Lane Module的状态转换和操作模式。
一个通道模块可能包含一个高速发送器(HS-TX)、一个高速接收器(HS-RX)或两者都有。在单个通道模块内,高速发送器和高速接收器在正常操作期间永远不会同时启用。启用的高速功能应当在其通道互联一侧终止通道,如第9.1.1节和第9.2.1节所定义。如果通道模块中的高速功能未启用,则该功能应置于高阻态。
在MIPI接口的物理层设计中,每个通道模块(Lane Module)可以包含发送和接收功能,即可能只有高速发送器(HS-TX)、只有高速接收器(HS-RX)或者两者都具备。但是,在正常工作状态下,同一个通道模块内的发送器和接收器不会同时被激活。这样做的目的是为了避免信号冲突和资源浪费,确保数据传输的正确性和可靠性。
当通道模块的高速功能被启用时,发送器(HS-TX)会在其一侧的通道互联上产生信号,同时接收器(HS-RX)会在同一侧接收信号。根据MIPI规范的第9.1.1节和第9.2.1节的要求,发送器需要在通道互联的发送端正确地终止信号,以确保信号的完整性和稳定性。
如果通道模块的高速功能没有被启用,那么该模块应当进入高阻态。高阻态意味着该通道模块不会对信号线路产生影响,从而避免了可能的信号干扰和不必要的功耗。这种设计允许系统根据实际需要灵活地启用或关闭各个通道模块的高速功能,以适应不同的工作条件和性能要求。
争用检测器(LP-CD)。低功耗功能总是成对出现的,因为这些是单端功能,分别在两条互联线上单独操作。高速和低功耗功能的存在是相关的。也就是说,如果一个通道模块包含一个高速发送器(HS-TX),它也应当包含一个低功耗发送器(LP-TX)。类似的限制也适用于高速接收器(HS-RX)和低功耗接收器(LP-RX)。如果包含一个低功耗接收器(LP-RX)的通道模块被供电,那么该LP-RX应当始终处于活跃状态,并持续监控线路电平。低功耗发送器(LP-TX)仅在驱动低功耗状态时才被启用。争用检测器功能仅在双向操作中需要。如果存在,当LP-TX驱动低功耗状态时,争用检测器功能被启用以检测争用情况。在ULPS(超低功耗状态)之外,LP-CD在驱动线路上新状态之前检查争用。单个通道模块中LP-TX、HS-TX和HS-RX的活动是相互排斥的,除了一些短暂的交叉时期。有关线路侧时钟和数据信号以及HS-TX、HS-RX、LP-TX、LP-RX和LP-CD功能的详细规范,请参见第9节和第10节。为了正常操作,通道互联两侧的通道模块中的一组功能必须匹配。这意味着对于通道互联一侧的每个HS和LP发送或接收功能,另一侧必须存在一个补充的HS或LP接收或发送功能。此外,在任何结合了发送和接收功能的通道模块中,都需要一个争用检测器。
总线的作用?
在 Linux 内核 的设备驱动模型中,关心总线、设备和驱动这 3 个实体,总线将设备和驱动绑定。 在系统每注册一个设备的时候, 会寻找与之匹配的驱动; 相反的, 在系统每注册一个驱动的时候,会寻找与之匹配的设备,而匹配由总线完成。一个现实的Linux 设备和驱动通常都需要挂接在一种总线上,对于不属于某一实际总线的设备,比如 led, lcd,rtc 等设备, 系统为了对它们进行统一管理, 虚拟出了一条总线出来,称为 platform(平台) 总线, 相应的设备称为 platform_device, 而驱动成为把驱动和设备资源进行分离:增加驱动的可移植性。所以说,驱动代码应该只包含与具体设备无任何关系的代码,只针对当前驱动所要操作的设备,提供出一套通用的操作方法。设备的信息应该都是由平台来描述,而不是在驱动中定义,驱动中应该从平台设备中获得具体信息
Linux 内核平台设备驱动优点:
1)当有驱动被加载时,总线会寻找驱动所对应的设备:
当加载驱动模块时, 平台总线会把驱动结构中的 name 成员取出和平台总线当前设备
链表上全部设备的 name 成员进行逐个比较, 如果发现相同, 就把这个驱动和这个设
备绑定上,然后驱动的探测函数会被调用。
2)当有设备被加载时,总线会寻找设备所对应的驱动:
当加载设备模块时, 平台总线会把设备结构中的 name 成员取出和平台总线当前驱动
链表上全部驱动的 name 成员进行逐个比较, 如果发现相同, 就把这个驱动和这个设
备绑定上,
3) 资源统一由内核管理,安全可靠:
将设备本身的资源使用统一的接口注册进内核, 由内核统一管理, 在驱动程序使
用这些资源时使用统一的接口,这样提高了驱动程序的可移植性. 所有要使用的资源都是以下流程:
探测资源–>向内核申请资源—>内核审批后—>映射—>使用
这个工作是由探测函数来完成。
缺点:
1. 驱动代码和硬件数据杂合在一起, 虽然设备的驱动方式还是一样, 但是硬件信息
变化后,需要重新修改驱动源代码。
2. 系统中硬件的信息都是驱动人员以各种方式提交给内核的,系统不方便管理
驱动代码的重复编写,会让开发效率比较低,为了提高开发效率,我们就要吧通用的代码和有差异的代码分离出来,来增强我们驱动代码的可移植性。所以,设备驱动分离的思想就应运而生了,在Linux中,我们是在写代码的时候进行分离。分离是把一些不相似的东西放到device.c,把相似的东西放到driver.c,如果有很多相似的设备或者平台,我们只要修改device.c就可以了,这样我们重复性的工作就大大的减少了。这就是平台总线的由来。
平台总线的缺陷:当我们用这个方法用习惯以后就会发现,假如soc不变,我们每换一个平台,都要修改C文件,并且还要重新编译。而且会在arch/arm/plat-xxx和arch/arm/mach-xxx下面留下大量关于板级细节的代码。并不是说这个方法不好,只是从Linux的发展来看,这些代码相对于Linux内核来说就是“垃圾代码”,而且这些“垃圾代码”非常多。
为了改变这个现状,设备树也就被引进到Linux上了,用来剔除相对内核来说的“垃圾代码”,即用设备树文件来描述这些设备信息,也就是代替device.c文件,虽然拿到了内核外面,但是platform匹配基本不变,并且相比于之前的方法,使用设备树不仅可以去掉大量的“垃圾代码”,并且采用文本格式,方便阅读和修改,如果需要修改部分资源,我们也不用在重新编译内核了,只需要把设备树源文件编译成二进制文件,在通过bootloader传递给内核就可以了。内核对其进行解析和展开得到关于硬件的拓扑图。我们通过内核提供的接口剖获取设备树的节点和属性就可以了。即内核对于同一soc的不同主板,只需要换设备树文件dtb即可实现不同主板的无差异支持,而无需更换内核文件。
这部分的驱动源码学习,主要是从下面的源码文件开始学习:
源码解析:
上面有一个struct platform_driver结构体,我们来看一下他的定义:
struct platform_driver {int (*probe)(struct platform_device *);int (*remove)(struct platform_device *);void (*shutdown)(struct platform_device *);int (*suspend)(struct platform_device *, pm_message_t state);int (*resume)(struct platform_device *);struct device_driver driver;const struct platform_device_id *id_table;bool prevent_deferred_probe;
};
(*probe)(struct platform_device *):
这是一个函数指针,指向一个函数
,该函数在平台设备被内核发现并准备使用时被调用。
probe 函数通常用于初始化硬件、注册设备或资源等。(*remove)(struct platform_device *):
这也是一个函数指针,指向一个函数,
该函数在平台设备被移除或系统关闭时被调用。
remove 函数通常用于执行与 probe 函数相反的操作,
如释放资源、注销设备等。(*shutdown)(struct platform_device *):
这个函数指针指向一个函数,该函数在系统关闭或重启时被调用。
shutdown 函数可以用来执行设备关闭前的最后一步操作。(*suspend)(struct platform_device *, pm_message_t state):
这个函数指针指向一个函数,该函数在设备被挂起(如系统进入睡眠状态)
时被调用。suspend 函数用于准备设备进入低功耗状态。(*resume)(struct platform_device *):
这个函数指针指向一个函数,该函数在设备从挂起状态恢复时被调用。
resume 函数用于恢复设备的操作状态。struct device_driver driver:
这是一个 device_driver 结构体,包含了与设备驱动相关的信息和操作。
它通常包含设备驱动的名称、驱动模型等信息。const struct platform_device_id *id_table:
这是一个指向 platform_device_id 结构体数组的指针,
该数组包含了驱动支持的设备ID。内核使用这些ID来匹配和绑定相应的平台设备。bool prevent_deferred_probe:
这是一个布尔值,指示是否应该延迟探测操作。如果设置为 true,
则内核不会在设备添加后立即进行探测,而是等待直到有特定的事件发生。
这里还有一个结构体device_driver :
/*** struct device_driver - The basic device driver structure* @name: Name of the device driver.* @bus: The bus which the device of this driver belongs to.* @owner: The module owner.* @mod_name: Used for built-in modules.* @suppress_bind_attrs: Disables bind/unbind via sysfs.* @probe_type: Type of the probe (synchronous or asynchronous) to use.* @of_match_table: The open firmware table.* @acpi_match_table: The ACPI match table.* @probe: Called to query the existence of a specific device,* whether this driver can work with it, and bind the driver* to a specific device.* @remove: Called when the device is removed from the system to* unbind a device from this driver.* @shutdown: Called at shut-down time to quiesce the device.* @suspend: Called to put the device to sleep mode. Usually to a* low power state.* @resume: Called to bring a device from sleep mode.* @groups: Default attributes that get created by the driver core* automatically.* @pm: Power management operations of the device which matched* this driver.* @coredump: Called when sysfs entry is written to. The device driver* is expected to call the dev_coredump API resulting in a* uevent.* @p: Driver core's private data, no one other than the driver* core can touch this.** The device driver-model tracks all of the drivers known to the system.* The main reason for this tracking is to enable the driver core to match* up drivers with new devices. Once drivers are known objects within the* system, however, a number of other things become possible. Device drivers* can export information and configuration variables that are independent* of any specific device.*/
struct device_driver {const char *name;struct bus_type *bus;struct module *owner;const char *mod_name; /* used for built-in modules */bool suppress_bind_attrs; /* disables bind/unbind via sysfs */enum probe_type probe_type;const struct of_device_id *of_match_table;const struct acpi_device_id *acpi_match_table;int (*probe) (struct device *dev);int (*remove) (struct device *dev);void (*shutdown) (struct device *dev);int (*suspend) (struct device *dev, pm_message_t state);int (*resume) (struct device *dev);const struct attribute_group **groups;const struct dev_pm_ops *pm;void (*coredump) (struct device *dev);struct driver_private *p;
};
下面,我们开始来看rockchip_mipidphy_probe接口:
这个接口首先传入了一个结构体指针,我们先来看一下platform_device结构体的定义:
struct platform_device {const char *name;int id;bool id_auto;struct device dev;u32 num_resources;struct resource *resource;const struct platform_device_id *id_entry;char *driver_override; /* Driver name to force a match *//* MFD cell pointer */struct mfd_cell *mfd_cell;/* arch specific additions */struct pdev_archdata archdata;
};const char *name:
这是一个指向字符串的指针,表示平台设备的名称。
这个名称通常是由设备制造商定义的,用于标识设备,并且在内核日志和设备信息中显示。int id:
这是一个整型成员,用于唯一标识一个平台设备实例。在某些情况下,
内核可能会使用这个ID来区分不同的设备实例。bool id_auto:
指示 id 成员是否由内核自动分配。如果设置为 true,则内核会在设备注册时自动生成一个唯一的ID。struct device dev:
包含了与设备相关的信息,如设备类型、设备类、设备编号等。
这个结构体是设备模型的一部分,用于管理内核中的设备。u32 num_resources:
这是一个无符号整型成员,表示平台设备声明的资源数量。资源通常包括内存区域、中断号等。struct resource *resource:
这是一个指向 resource 结构体数组的指针,
每个 resource 结构体描述了设备的一个资源,如内存区域、中断号等。const struct platform_device_id *id_entry:
这是一个指向 platform_device_id 结构体的指针,
该结构体包含了设备的ID信息,用于内核在设备树中匹配设备。char *driver_override:
这是一个指向字符的指针,用于强制设备与指定的驱动程序匹配。
这在某些特殊情况下可能需要,例如,当设备制造商提供了一个特定的驱动程序时。struct mfd_cell *mfd_cell:
这是一个指向 mfd_cell 结构体的指针,用于管理多功能设备(Multi-Function Device,MFD)。MFD是一种设备,它可以同时提供多个不同的功能或子设备。struct pdev_archdata archdata:
这是一个结构体,用于存储架构相关的平台设备数据。
archdata 结构体的具体内容取决于内核的架构,用于提供特定于架构的信息。
接着是rockchip_mipidphy_probe接口里面的mipidphy_priv结构体,mipidphy_priv 结构体为MIPI DPHY驱动程序提供了必要的硬件信息和控制接口,使得驱动程序能够有效地管理硬件设备和媒体流。通过这个结构体,驱动程序可以访问和控制硬件寄存器、管理时钟资源、处理异步通知、同步并发访问,并与V4L2框架进行交互:
struct mipidphy_priv {struct device *dev;//表示与驱动程序关联的设备。它用于设备管理,如设备注册、注销等。struct regmap *regmap_grf;//用于映射和访问通用寄存器文件(General Register File,GRF)的寄存器。regmap 提供了一种灵活的寄存器访问接口const struct dphy_reg *grf_regs;//包含了GRF寄存器的定义和布局const struct txrx_reg *txrx_regs;//包含了TX/RX通道寄存器的定义和布局const struct csiphy_reg *csiphy_regs;//包含了CSI PHY寄存器的定义和布局void __iomem *csihost_base_addr;//这是一个指向内存映射I/O区域的指针,表示CSI主机接口的基地址struct clk *clks[MAX_DPHY_CLK];//这是一个指向时钟结构体数组的指针,用于管理设备的所有时钟资源。const struct dphy_drv_data *drv_data;//包含了驱动程序特定的数据和配置信息u64 data_rate_mbps;//表示设备支持的最大数据传输速率,单位是Mbps。struct v4l2_async_notifier notifier;//用于V4L2异步通知机制,允许设备在连接或断开时发送通知。struct v4l2_subdev sd;//表示一个V4L2子设备,用于V4L2框架中的设备管理和媒体流控制。struct mutex mutex; /* lock for updating protection */struct media_pad pads[MIPI_DPHY_RX_PADS_NUM];//表示媒体设备的连接端口。MIPI_DPHY_RX_PADS_NUM 是端口数量的宏定义。struct mipidphy_sensor sensors[MAX_DPHY_SENSORS];//用于存储和管理连接到MIPI DPHY的sensors信息int num_sensors;//表示当前连接到MIPI DPHY的sensors数量int phy_index;//bool is_streaming;//指示设备是否正在处理媒体流void __iomem *txrx_base_addr;//这是一个指向内存映射I/O区域的指针,表示TX/RX通道的基地址。int (*stream_on)(struct mipidphy_priv *priv, struct v4l2_subdev *sd);//该函数在开始媒体流传输时被调用int (*stream_off)(struct mipidphy_priv *priv, struct v4l2_subdev *sd);//该函数在停止媒体流传输时被调用
};
下面是dphy_drv_data结构体:
struct dphy_drv_data {const char * const *clks;//包含了设备所需的时钟名称。这些时钟名称用于在设备初始化时请求相应的时钟资源。int num_clks;//表示 clks 数组中时钟名称的数量。const struct hsfreq_range *hsfreq_ranges;//包含了设备支持的高速频率范围。这些范围定义了设备在不同工作模式下可以支持的时钟频率。int num_hsfreq_ranges;//表示 hsfreq_ranges 数组中高速频率范围的数量。const struct dphy_reg *grf_regs;//包含了设备通用寄存器(GRF)的定义和布局const struct txrx_reg *txrx_regs;//包含了设备TX/RX通道寄存器的定义和布局。const struct csiphy_reg *csiphy_regs;//包含了设备CSI PHY寄存器的定义和布局enum mipi_dphy_ctl_type ctl_type;//表示MIPI DPHY的控制类型。这个成员指示了设备应该如何被控制,例如,是通过硬件控制还是软件控制void (*individual_init)(struct mipidphy_priv *priv);//这个函数在设备初始化时被调用,用于执行设备特定的初始化操作。enum mipi_dphy_chip_id chip_id;//表示MIPI DPHY芯片的ID。这个成员用于标识设备的具体型号或版本
};
一、I2c驱动学习:
这里我主要以之前讲解的gc2053sensor驱动源码,来学习i2c驱动,因为在现在sensor和soc,基本都是用i2c来进行通信,很少有spi;所以i2c驱动框架的原理技术,我们必须要去深挖一下:
上面截图我们可以看到,这里我晚点再给大家分析!
这里我先详细分析一下I2c驱动里面重要的结构体:
struct i2c_driver结构体:
struct i2c_driver {
unsigned int class;
/* Notifies the driver that a new bus has appeared. You should avoid
* using this, it will be removed in a near future. */
int (*attach_adapter)(struct i2c_adapter *) __deprecated;
/* Standard driver model interfaces */
int (*probe)(struct i2c_client *, const struct i2c_device_id *);
int (*remove)(struct i2c_client *);
/* driver model interfaces that don't relate to enumeration */
void (*shutdown)(struct i2c_client *);
int (*suspend)(struct i2c_client *, pm_message_t mesg);
int (*resume)(struct i2c_client *);
/* Alert callback, for example for the SMBus alert protocol.
* The format and meaning of the data value depends on the protocol.
* For the SMBus alert protocol, there is a single bit of data passed
* as the alert response's low bit ("event flag"). */
void (*alert)(struct i2c_client *, unsigned int data);
/* a ioctl like command that can be used to perform specific functions
* with the device. */
int (*command)(struct i2c_client *client, unsigned int cmd, void *arg);
struct device_driverdriver;
const struct i2c_device_id *id_table;
/* Device detection callback for automatic device creation */
int (*detect)(struct i2c_client *, struct i2c_board_info *);
const unsigned short *address_list;
struct list_headclients;
};
二、V4L2源码驱动学习:
源码:
linux-4.20.zip(192.7 MB)
这里主要记录一下v4l2的整个框架的学习,后面也会记录alsa的整个框架的学习,先一个一个脚步来!
这里我以Linux内核4.2的版本来学习v4l2!
首先v4l2核心的驱动代码,在Linux内核里面的drivers/media/v4l2-core:
首先,在刚才接触的话,可能不太明白这些源码文件是干嘛用的,所以结合网上一些学习资料,我们先搞清楚这些源码文件的作用是啥,然后再切入学习:
v4l2-dev.c:视频捕捉接口,主要结构体video_device的注册。
v4l2-common.c:在 Linux 操作系统体系采用低级别的操作一套设备 struct tures/vectors 的通用视频设备接口;
v4l2-device.c: v4l2设备支持,注册v4l2_device.
v4l2-subdev.c:v4l2子设备:
v4l2-async.c : V4L2异步子设备注册API.
v4l2-ctrls.c : V4L2控制框架实现。
v4l2-event.c : v4l2 事件。
v4l2-fh.c : v4l2文件处理
v4l2-fwnode.c : V4L2 fwnode绑定解析库;V4L2 fwnode库的起源位于V4L2 of库中,该库以前位于V4L2 -of.c中。
v4l2-ioctl.c: 处理 V4L2 的 ioctl 命令的一个通用的框架
v4l2-mc.c : 媒体控制器辅助功能
v4l2-mem2mem.c: 用于Linux 2和videobuf的视频的内存到内存设备框架;为源和目标都使用videobuf缓冲区的设备提供的辅助函数。
videobuf-core.c:用于处理video4linux捕获缓冲区的通用帮助函数.
videobuf-dma-contig.c: 用于物理上连续捕获缓冲区的辅助函数;这些功能支持缺乏分散收集支持的硬件(即;缓冲在物理内存中必须是线性的)
videobuf-dam-sg.c: 用于SG DMA video4linux捕获缓冲区的辅助函数;这些函数期望硬件能够分散收集(即缓冲区在物理内存中不是线性的,而是分成PAGE_SIZE块)。他们还假设驱动不需要触摸视频数据。
videobuf-vmalloc.c : 用于vmalloc video4linux捕获缓冲区的辅助函数;
media-device.c : 媒体设备
media-devnode.c : 媒体设备节点
media-entity.c: 媒体的实体
media-request.c : 媒体设备请求对象