文章目录
- 前言
- 一、I2C 物理结构以及基本原理
- 1.1硬件连接结构
- 1.2主从关系
- 二、Linux系统下的 I2C 节点结构
- 2.1Linux I2C 子系统三大部分
- 三、具体实验代码内容
- 3.1 修改设备树
- 3.2 写寄存器头文件
- 3.3写 AP3216C I2C 驱动:把 I2C 设备包装成 Linux 可访问设备
- 3.4 写用户态测试 APP:用 open/read 验证驱动是否工作
- 四、MIPI摄像头 V4L2相关设备搭建
- 总结
前言
本章内容:了解I2C控制器驱动,并在理解适配器驱动下,自己写挂在 I2C 总线下面的设备驱动
一、I2C 物理结构以及基本原理
1.1硬件连接结构
共有两条线:SCL-串行时钟线,SDA-串行数据线,支持一个主机挂多个从机,每个从机通过不同的 I2C 地址区分。
1.2主从关系
主机一般负责数据发送(包括从机地址,读写数据,开始结束信号等),从机只根据主机命令接收或发送数据。
二、Linux系统下的 I2C 节点结构
第一层:SoC 的 I2C 控制器
第二层:挂在 I2C 控制器下面的 I2C 从设备
2.1Linux I2C 子系统三大部分
大致可以分成三部分
- I2C core:负责统一管理 I2C 总线、设备、驱动注册和匹配
- I2C adapter:表示 SoC 里的一个 I2C 控制器,比如 i2c5
- I2C client; driver:表示挂在 I2C 总线下面的某个从设备,比如 AP3216C;表示这个从设备对应的软件驱动,比如 ap3216c_driver
rk3x_i2c_probe的作用:
- 申请 rk3x_i2c 私有结构体
- 解析设备树里的 I2C 时序和频率
- 初始化 i2c_adapter
- 设置 i2c_algorithm
- 映射 I2C 控制器寄存器
- 获取中断号并注册中断
- 获取并准备时钟
- 调用 i2c_add_adapter() 注册 adapter
具体流程再platform驱动有讲(后续讲解)
三、具体实验代码内容
可以分为五个部分
3.1 修改设备树
3.2 写 AP3216C 寄存器头文件
3.3 写 AP3216C I2C 驱动
3.4 写用户态测试 APP
3.5 编译、加载、运行测试
3.1 修改设备树
修改设备树,增加节点,以及节点内的compatible和从机地址等其他硬件资源(I2C的reg是从机地址,但是其他的不一定,spi:片选号 chip select)
3.2 写寄存器头文件
AP3216C 是一个真实存在的 I2C 外设芯片。
它有自己的数据手册,里面规定了 I2C 地址、寄存器地址、寄存器含义、数据格式和初始化时序。
驱动开发的工作,就是把这些手册内容翻译成内核代码:
1. 设备树描述这个芯片挂在哪条 I2C 总线上
2. 驱动代码根据 compatible 匹配这个芯片
3. 驱动通过 I2C 读写 AP3216C 的寄存器
4. 驱动把这个芯片注册成 Linux 能管理的设备
5. 如果需要用户态访问,再创建 /dev/ap3216c 这种设备节点
头文件中需要写数据寄存器的地址,同时设置子设备的工作模式,需要把芯片手册里的“寄存器表/命令表”翻译成驱动代码能直接使用的宏。
PS:0x0F 对应什么功能,不是我们自己定的,而是芯片厂商在硬件内部已经设计好的。数据手册只是把这个固定规则告诉我们。
3.3写 AP3216C I2C 驱动:把 I2C 设备包装成 Linux 可访问设备
第一件事:注册 i2c_driver,让驱动能和设备树里的 i2c_client 匹配。
第二件事:注册字符设备,让用户态能通过 /dev/ap3216c 访问传感器。
AP3216C 实验其实有两条线。
第一条线:设备发现和驱动匹配线->i2c_client、i2c_driver、bus、probe绑定client和字符设备节点,实现用户和内核间的数据交换(这里具体是驱动拿到client就可以通过驱动内部读写函数函数访问硬件了)
第二条线:用户态访问线->alloc_chrdev_region()、cdev_init()、cdev_add()等创建用户态节点
只有这样才可以实现用户与内核态的数据交换
在 device 和 driver 匹配成功、probe 被调用之后,
驱动会进一步把这个client绑定某设备(字符设备、subv4l2设备)到某个 Linux 子系统中。
如果注册到字符设备框架,就形成 /dev/xxx;
如果注册到 V4L2/media 框架,就形成 v4l2_subdev/media entity;
根据设备所在总线类型,选择对应 driver 结构体;根据设备通信方式,封装对应读写函数;根据用户态访问方式,注册对应 Linux 子系统接口。
3.4 写用户态测试 APP:用 open/read 验证驱动是否工作
四、MIPI摄像头 V4L2相关设备搭建
具体流程如下(这里是从我记得笔记拉下来的,csdn的引用不太好看,需要的同学可以直接复制下来让AI给你详细讲清楚):
设备树:
&i2c2 {
ov13855@36 {
compatible = “ovti,ov13855”;
reg = <0x36>;
reset-gpios = <…>;
clocks = <…>;
port {
endpoint {
remote-endpoint = <&csi_in>; };
};
↓
内核解析设备树:
创建 i2c_client
/sys/bus/i2c/devices/2-0036
↓
加载 ov13855 驱动:
i2c_add_driver(&ov13855_i2c_driver)
↓
I2C bus 匹配:
compatible 匹配成功
↓
调用 ov13855_probe(client)
↓
probe 中:
申请 ov13855 私有结构体
保存 client
获取 clk/gpio/regulator
power on
I2C 读 chip ID
初始化 v4l2_subdev
初始化 media pad
初始化 controls
注册 async subdev
↓
media framework:
sensor subdev 等待和 CSI/ISP 绑定
形成 media graph
↓
用户态:
open(“/dev/videoX”)
VIDIOC_S_FMT
VIDIOC_REQBUFS
VIDIOC_STREAMON
↓
capture 驱动启动 pipeline:
调用 sensor s_stream(1)
↓
sensor 驱动:
I2C 写寄存器表
I2C 写 stream on
↓
sensor 开始通过 MIPI CSI-2 输出图像
↓
CSI / ISP / video capture 接收图像
↓
用户态 DQBUF 获取图像帧
总结
下面以正点原子教程中的I2C字符实验内容来总结,V4L2结构可以看上面的流程,基本原理是一样的,只不过根据注册的不同设备(字符设备、V4L2等可能具体驱动和字符设备注册流程不同):
AP3216C 的寄存器地址由芯片数据手册规定,驱动文件通过引用 ap3216creg.h,把这些寄存器地址宏用于 I2C 读写函数中。
设备树负责描述 AP3216C 挂在哪条 I2C 总线上、I2C 地址是多少,以及可能需要的 GPIO、clock、regulator 等资源。内核解析设备树后创建 i2c_client,i2c_client 表示 AP3216C 在 I2C 总线模型中的设备对象,里面保存 I2C 地址、所属 adapter 以及通用 device 信息。
ap3216c_driver 注册到 I2C 子系统后,与 i2c_client 匹配成功并进入 probe。probe 中申请驱动私有结构体 ap3216c_dev,把 i2c_client 保存进去,同时注册字符设备 cdev,创建 /dev/ap3216c。这样用户态通过 /dev/ap3216c 调用 open/read 时,会进入 file_operations 对应函数,而这些函数内部再通过 ap3216c_read_regs/ap3216c_write_regs 和 i2c_transfer 访问 AP3216C 硬件寄存器。
注意client中储存了:
- I2C 地址
- 所属 I2C adapter 表示这个设备挂在哪个 I2C 控制器下面
3.compatible = “ovti,ov13855” 参与和 i2c_driver 的 of_match_table 匹配 - 通用 struct device
- 这个 struct device 里关联着原始设备树节点(后续其他内容需要驱动来读取并储存)
本质上因为client是适配很多的,他们可能有不同的子设备都叫做client,都写进去太臃肿了