PX3SE上camera相关流程记录

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