1.简介
摄像头:使用ov2659 内置ISP,200W像素(同时支持RAW Bayer输出),镜头等信息(小镜头,固定焦距) 主控:使用瑞芯微PX3SE (RK3128的精简版本,偏工业,温度范围广),该芯片内部无ISP处理模块,所以 摄像头需要驱动带ISP处理模块。芯片上使用DVP接口进行连接OV2659传感器。
2.驱动流程
Linux上首先是确认硬件是否正常工作,设备树先进行配置,在调用相关初始化时序后,可以I2C tool确认一下, 是否可以正常识别到摄像头(这步是必须流程,如果无法识别到摄像头,一定要先检查摄像头时序、电路电压等等) 保证硬件正常工作。
2.1 设备树相关配置
这个平台上,OV2659 sensor ---> CIF控制器 --->V4L2 queue ---> 上层应用
&cif_new {
status = "okay";
port {
cif_in: endpoint {
remote-endpoint = <&ov2659_out>; // 连接到 OV2659
vsync-active = <0>; // VSYNC 低电平有效
hsync-active = <1>; // HSYNC 高电平有效
};
};
};
ov2659@30 {
compatible = "ovti,ov2659"; // 匹配 OV2659 驱动
reg = <0x30>; // I2C 从地址 0x30
pinctrl-0 = <&cam0_pwdn>;
clocks = <&cru SCLK_CIF_OUT>; // 使用 CIF 输出时钟作为 XVCLK
clock-names = "xvclk";
avdd-supply = <&dvdd_3v3>; // 模拟电源 3.3V
dvdd-supply = <&dvdd_1v8>; // 核心电源 1.8V
dovdd-supply = <&vcc_cvbs_33>; // I/O 电源 2.9V (LDO4)
pwdn-gpios = <&gpio3 31 GPIO_ACTIVE_HIGH>; // GPIO3_D7 掉电控制
port {
ov2659_out: endpoint {
remote-endpoint = <&cif_in>; // 连接到 CIF
link-frequencies = /bits/ 64 <70000000>; // 70MHz 像素时钟
};
};
};
2.2 驱动流程以及框架
这个瑞芯微上使用V4L2框架进行,定义的数据结构和相关OPS
//主要使用的数据结构
struct ov2659 {
struct i2c_client *client; // I2C 客户端
struct clk *xvclk; // 外部输入时钟 (24MHz)
struct gpio_desc *pwdn_gpio; // 掉电 GPIO
struct regulator_bulk_data supplies[3]; // avdd/dvdd/dovdd 三路电源
struct v4l2_subdev subdev; // V4L2 子设备
struct media_pad pad; // Media pad (source)
const struct ov2659_mode *cur_mode; // 当前输出模式
};
//相关的subdev ops
static const struct v4l2_subdev_ops ov2659_subdev_ops = {
.video = &ov2659_video_ops, // .s_stream = ov2659_s_stream
.pad = &ov2659_pad_ops, // .enum_mbus_code, .enum_frame_size,
// .get_fmt, .set_fmt
};
然后从compatible属性进行匹配,回调probe,主要有RKCIF模块的probe,还有sensor的probe RKCIF模块的probe,厂商已经写好了,使用VB2进行
platform_driver (rkcif_plat_drv)
├── of_match: "rockchip,rk3128-cif"
└── rkcif_plat_probe()
├── devm_ioremap_resource(0x1010a000) // 映射寄存器
├── devm_request_irq(irq=8) // 注册中断
├── devm_clk_get(aclk/hclk/sclk_cif_out) // 获取时钟
├── rkcif_stream_init() // 初始化 stream
│ └── 默认格式 NV12 640x480
├── v4l2_device_register() // 注册 V4L2 设备
├── media_device_register() // 注册 Media 设备
├── rkcif_register_stream_vdev() // 注册 /dev/videoX
│ ├── vb2_dma_contig_init_ctx() // DMA 分配上下文
│ ├── vb2_queue_init() // 初始化 VB2 队列
│ ├── video_register_device() // 注册 video 设备
│ └── media_entity_init() // 初始化 media pad
├── cif_subdev_notifier() // 异步通知器
│ ├── v4l2_async_notifier_parse_fwnode_endpoints()
│ │ └── rkcif_fwnode_parse() // 解析 DTS 端点
│ └── v4l2_async_notifier_register()
└── of_reserved_mem_device_init() // 预留内存(无IOMMU)
sensor ov2659模块的probe(这个部分是驱动摄像头的关键修改部分)
i2c_driver (ov2659_i2c_driver)
ov2659_probe()
├── devm_kzalloc() // 分配 ov2659 结构体
├── devm_clk_get(dev, "xvclk") // 获取 XVCLK 时钟
├── clk_set_rate(xvclk, 24000000) // 设置 XVCLK 为 24MHz
├── devm_gpiod_get(dev, "pwdn", ...) // 获取 pwdn GPIO
├── devm_regulator_bulk_get() // 获取 avdd/dvdd/dovdd 电源
├── ov2659_power_on() // 上电: pwdn=0, 使能电源, 延时
├── ov2659_check_sensor_id() // 读取 chip ID
├── v4l2_i2c_subdev_init() // 初始化 V4L2 subdev
├── media_entity_pads_init(1 pad, SOURCE) // 初始化 media entity
└── v4l2_async_register_subdev() // 异步注册到 V4L2 核心
3.摄像头工作流程
主要含有,初始化阶段,开流阶段(预览之类的),捕获(拍照类),关流阶段。因为这个PX3SE,是拍照主要是从流里面获取 一帧图像,然后调整(有区别与高级的,比如RK3399,有ISP,支持多路流)。
3.1 开流阶段
用户空间: VIDIOC_STREAMON
→ vb2_ioctl_streamon()
→ vb2_streamon()
→ rkcif_start_streaming()
├── get_active_sensor() // 通过 media link 找活跃 sensor
├── rkcif_sanity_check_fmt() // 验证输入输出格式
├── rkcif_create_dummy_buf() // dma_alloc_coherent()
├── pm_runtime_get_sync() // 使能 CIF 时钟
├── rkcif_stream_start() // ★ 配置 CIF 硬件寄存器
│
├── v4l2_subdev_call(sensor, core, s_power, 1) // sensor上电
└── v4l2_subdev_call(sensor, video, s_stream, 1) // sensor开流
→ ov2659_s_stream(1)
├── pm_runtime_get_sync()
├── ov2659_write_array(global_setting)
├── ov2659_write_array(cur_mode->reg_list)
└── ov2659_write(0x0100, 0x01) // 开始输出
3.2 捕获阶段
(px3se拍照,其实也是从这个里面取,然后调整分辨率之类的,有区别于高级的) 里面有打开相关的中断 FRAME_END: 一帧数据已完整写入内存的标志(这是决定 buffer 可交付用户空间的唯一关键中断) PST_INF_FRAME_END: FIFO 就绪信号,在 FRAME_END 之前触发(没有重点关注)
//中断处理:(重点关注的)
rkcif_irq_handler()
└── rkcif_irq_oneframe()
//重点函数(打开CIF里面中断)
write_cif_reg(base, CIF_INTEN, FRAME_END_EN | PST_INF_FRAME_END);
//用户空间: VIDIOC_DQBUF 返回已完成帧的 buffer 索引
3.3 关流阶段
用户空间: VIDIOC_STREAMOFF
→ vb2_ioctl_streamoff()
→ rkcif_stop_streaming()
4.用户空间使用
简单说明
用户空间的两种等待方式 (videobuf2 框架透明处理):
A. 阻塞: ioctl(VIDIOC_DQBUF)
→ 内核在 vb2_dqbuf() 中调用 wait_event_interruptible()
→ 等待 done_list 非空
→ vb2_buffer_done 唤醒后返回 buffer 给用户
B. 非阻塞: poll() / select() / epoll()
→ .poll = vb2_fop_poll
→ 等待 POLLIN 事件
→ vb2_buffer_done 触发 poll 返回 POLLIN
数据帧相关流程,上层开流后,不断写入这些buffer,中断来了,更新这些buffer状态。
QBUF ──→ buf_head: [B0] ← 用户入队 3 个 buffer
STREAMON ──→ CIF 开始采集
→ B0 地址写入 CIF 寄存器 ← curr_buf = B0
→ sensor 开始输出帧
帧1完成 ──→ FRAME_END 中断
→ vb_done = B0 ← B0 已填满
→ 下一个 B1 写入 CIF 寄存器
→ vb2_buffer_done(B0) ← 唤醒用户
→ 用户 DQBUF 返回 B0 ← 用户拿到第一帧
帧2完成 ──→ FRAME_END 中断
→ vb_done = B1
→ B2 (或用户刚 QBUF 的 B0) 写入 CIF 寄存器
→ vb2_buffer_done(B1) ← 唤醒用户
→ 用户 DQBUF 返回 B1
您还没有登录,请您登录后发表评论。