美文网首页
第一个摄像头驱动移植

第一个摄像头驱动移植

作者: 窝窝蜗牛 | 来源:发表于2019-09-26 11:31 被阅读0次

    操作步骤

    1. 设备树文件的修改

    新平台sdm429的设备树文件路径为kernel/msm-4.9/arch/arm64/boot/dts/qcom/,因为修改的是摄像头设备信息,修改文件为sdm429w-camera-sensor-spyro.dtsi

    qcom,camera@1 {
            cell-index = <1>;   //sensor标识
            compatible = "qcom,camera";   //设备与驱动相匹配的属性
            reg = <0x1>;  
            qcom,csiphy-sd-index = <1>;    //s_bundle->sensor_info->subdev_id[SUB_MODULE_CSIPHY] = 1
            qcom,csid-sd-index = <1>;     //s_bundle->sensor_info->subdev_id[SUB_MODULE_CSID] = 1
            qcom,mount-angle = <270>;
            cam_vdig-supply = <&pm660_l3>;    //DVDD供电(isp供电)
            cam_vio-supply = <&pm660_l14>;    //IOVDD供电(i2c供电)
            cam_vaf-supply = <&pm660_l19>;     //马达供电
            qcom,cam-vreg-name = "cam_vdig","cam_vana","cam_vio",        //供电输出配置
                                "cam_vaf";            
            qcom,cam-vreg-min-voltage = <1200000 2800000 1800000 2850000>;
            qcom,cam-vreg-max-voltage = <1200000 2800000 1800000 3200000>;
            qcom,cam-vreg-op-mode = <200000 0 80000 100000>;
            pinctrl-names = "cam_default", "cam_suspend";
            pinctrl-0 = <&cam_sensor_mclk1_default
                    &cam_sensor_front_default>;
            pinctrl-1 = <&cam_sensor_mclk1_sleep
                    &cam_sensor_front_sleep>;
            gpios = <&tlmm 27 0>,            //GPIO口配置
                <&tlmm 33 0>,
                <&tlmm 66 0>,
                <&tlmm 38 0>;
            qcom,gpio-vana= <1>;
            qcom,gpio-vdig= <2>;
            qcom,gpio-reset = <3>;
            qcom,gpio-req-tbl-num = <0 1 2 3>;
            qcom,gpio-req-tbl-flags = <1 0 0 0>;
            qcom,gpio-req-tbl-label = "CAMIF_MCLK1",
                "CAM_AVDD1",
                "CAM_DVDD1",
                "CAM_RESET1";
            qcom,sensor-position = <1>;   //后摄为0 前摄为1
            qcom,sensor-mode = <0>;     
            qcom,cci-master = <0>;  //一般来说只要前后摄i2c地址不冲突,配置为0即可
            clocks = <&clock_gcc clk_mclk1_clk_src>,                 
                    <&clock_gcc clk_gcc_camss_mclk1_clk>;
            clock-names = "cam_src_clk", "cam_clk";
            qcom,clock-rates = <24000000 0>;
        };
    

    2. 驱动文件和效果文件添加

    在vendor/qcom/proprietary/mm-camera/mm-camera2/mediacontroller/modules/sensors/sensor/libs目录下新建s5k4h7目录,在该目录下添加s5k4h7驱动文件,并编写Android.mk文件。在vendor/qcom/proprietary/mm-camera/mm-camera2/media-controller/modules/sensors/chromatix/0310目录下添加效果文件。

    3. 修改device-vendor.mk

    vi vendor/qcom/proprietary/common/config/device-vendor.mk,添加新增的模块。


    device-vender.mk

    4. 修改msm8937_camera.xml

    在vendor/qcom/proprietary/mm-camera/mm-camera2/media-controller/modules/sensors/configs/msm8937_camera.xml文件中添加如下CameraModuleConfig节点:

    <CameraModuleConfig>
        <CameraId>1</CameraId>
        <SensorName>s5k4h7</SensorName>
        <ChromatixName>s5k4h7_chromatix</ChromatixName>
        <ModesSupported>1</ModesSupported>
        <Position>BACK</Position>
        <MountAngle>90</MountAngle>
        <CSIInfo>
          <CSIDCore>0</CSIDCore>
          <LaneMask>0x1F</LaneMask>
          <LaneAssign>0x4320</LaneAssign>
          <ComboMode>0</ComboMode>
        </CSIInfo>
        <LensInfo>
          <FocalLength>2.94</FocalLength>
          <FNumber>2.4</FNumber>
          <TotalFocusDistance>1.5</TotalFocusDistance>
          <HorizontalViewAngle>63.2</HorizontalViewAngle>
          <VerticalViewAngle>49.7</VerticalViewAngle>
          <MinFocusDistance>0.1</MinFocusDistance>
        </LensInfo>
    </CameraModuleConfig>
    

    原理分析

    驱动移植虽然步骤不多,但是麻烦却一大堆,搞清楚其原理是一件很重要的事情。这里首先梳理下摄像头的初始化流程:在内核初始化过程中会将qcom,camera@1节点转换为platform_device,当设备和驱动匹配会调用驱动的probe函数,该函数读取设备节点信息并保存。另外,Qualcomm的camera守护进程端获取需要初始化的sensor,然后加载其驱动程序,并将驱动信息传递给内核,内核对其上电测试成功后创建/dev/video1设备文件节点。可以看出其最终目的还是创建一个设备文件节点给上层访问。
    这里以sensor_init.c作为切入点,sensor_init_probe->sensor_init_xml_probe

    static boolean sensor_init_xml_probe(module_sensor_ctrl_t *module_ctrl,
      int32_t sd_fd){
        ...
        num_cam_config = sensor_xml_util_get_num_nodes(rootPtr, "CameraModuleConfig");  //获取需要配置的sensor数量
        for (i = 0; i < num_cam_config; i++) {
            nodePtr = sensor_xml_util_get_node(rootPtr, "CameraModuleConfig", i); //获取第i个CameraModuleConfig节点
            xmlConfig.nodePtr = nodePtr;
            ret = sensor_xml_util_get_camera_probe_config(&xmlConfig, "CameraModuleConfig");  //获取probe配置信息
            rc = sensor_probe(module_ctrl,
                          sd_fd,
                          camera_cfg.sensor_name,
                          NULL,
                          &xmlConfig,
                          FALSE,
                          FALSE);
            ...
      }
    

    可以看出该函数是遍历xml文件中的CameraModuleConfig节点,解析节点信息,至于遍历的是哪个xml文件,可以从日志文件中分析,然后推出需要配置的xml文件。sensor_probe分析:

    static boolean sensor_probe(...){
        ...
        rc = sensor_load_library(sensor_name, sensor_lib_params, path);
        translate_sensor_slave_info(slave_info,
          &sensor_lib_params->sensor_lib_ptr->sensor_slave_info,
          xmlConfig->configPtr, power_up_setting, power_down_setting);
        memset(&cfg, 0, sizeof(cfg));
        cfg.cfgtype = CFG_SINIT_PROBE;
        cfg.cfg.setting = slave_info;
        if (ioctl(fd, VIDIOC_MSM_SENSOR_INIT_CFG, &cfg) < 0) {
            SINFO("[%s]CFG_SINIT_PROBE failed",sensor_name);
            ret = FALSE;
            goto ERROR;
        }  //发送probe指令给sensor_init
        ...
    }
    

    sensor_load_library主要是加载sensor的驱动信息,通过sensor name加载对应的.so文件,然后执行sensor_open_lib函数,获取sensor的各项配置参数,查看s5k4h7的驱动程序,该函数返回一个sensor_lib_t结构体地址。然后将各参数传入slave_info,传给内核。调用sensor_init设备的ioctl接口,最终调用到msm_sensor_driver_probe:

    int32_t msm_sensor_driver_probe(...){
        ...
        if (copy_from_user(slave_info,(void __user *)setting, sizeof(*slave_info))) {
            pr_err("failed: copy_from_user");
            rc = -EFAULT;
            goto free_slave_info;
        }   //获取mm-camera传来的slave_info
        s_ctrl = g_sctrl[slave_info->camera_id]; //获取msm_sensor_ctrl_t
        rc = msm_sensor_get_power_settings(setting, slave_info,
          &s_ctrl->sensordata->power_info);  //获取上下电流程
      
        camera_info = kzalloc(sizeof(struct msm_camera_slave_info), GFP_KERNEL);
        s_ctrl->sensordata->slave_info = camera_info;
        /* Fill sensor slave info */
        camera_info->sensor_id_reg_addr = slave_info->sensor_id_info.sensor_id_reg_addr;
        camera_info->sensor_id = slave_info->sensor_id_info.sensor_id; //保存sensor驱动程序配置信息
        ...
        rc = s_ctrl->func_tbl->sensor_power_up(s_ctrl); //sensor上电和probe
        ...
        if (s_ctrl->sensor_device_type == MSM_CAMERA_PLATFORM_DEVICE)
            rc = msm_sensor_driver_create_v4l_subdev(s_ctrl);
        else
            rc = msm_sensor_driver_create_i2c_v4l_subdev(s_ctrl);
        ...
    }
    

    s_ctrl->func_tbl->sensor_power_up=>msm_sensor_power_up,sensor上电分为两个步骤,第一步给sensor上电并初始化cci,第二步匹配id。因为每个sensor都是作为一个slave挂在cci总线上,而sdm429则是作为一个master,他们通讯需要遵循cci协议,所以只有sensor上电成功,sdm429才能与其通信,读取其id,而第二步id匹配则是比较slave_info中的id和读取的id是否相同。
    g_sctrl是一个全局变量,在platform_driver的probe函数中填充,这是一个很重要的结构体指针数组,包含了所有sensor的所有信息,也就是说设备树中有几个qcom,camera@1节点,g_sctrl就有几组数据,然后我们通过camera id索引得到对应的msm_sensor_ctrl_t结构体,该结构体的成员变量sensordata包含了camera的板级信息(供电、io口),对应着硬件资源,我们在probe的时候需要申请这些资源,不然cpu也不知道如何给sensor供电了。那现在最关键的就是查看g_sctrl在哪里填充的了,阅读代码可以看出,每匹配到一个camera设备,msm_sensor_driver_platform_probe执行一次,并申请一个msm_sensor_ctrl_t结构体:

    static int32_t msm_sensor_driver_platform_probe(struct platform_device *pdev)
    {
        s_ctrl = kzalloc(sizeof(*s_ctrl), GFP_KERNEL);
        ...
        rc = msm_sensor_driver_parse(s_ctrl);
    }
    static int32_t msm_sensor_driver_parse(struct msm_sensor_ctrl_t *s_ctrl)
    {
        rc = msm_sensor_driver_get_dt_data(s_ctrl); //获取设备树节点属性
        ...
        g_sctrl[s_ctrl->id] = s_ctrl;
    }
    static int32_t msm_sensor_driver_get_dt_data(struct msm_sensor_ctrl_t *s_ctrl)
    {
        ...
        rc = of_property_read_u32(of_node, "cell-index", &cell_id);
        s_ctrl->id = cell_id;
    }
    

    可以看出g_sctrl的填充索引值为设备树中的cell-index,获取该数组成员的索引值为sensor驱动的slave_info->camera_id,由xml文件读取得到。
    作为一个小白,我开始只写过裸板驱动,并不清楚驱动、设备跟总线的关系,所以一开始直接上手设备树是很懵的,一开始并不明白写了xxx_driver为什么还要写xxx_device,后来在学习platform总线的时候又一直在找platform_device在哪里定义,所以在此强烈建议小白学习一下设备驱动模型linux设备树

    问题总结

    1. read id failed

    在日志文件中搜索关键字match_id可以得到如下信息


    log

    读取id失败一般情况下是由于上电失败导致的,上电不成功,sensor是无法进行cci通信的,上电失败的原因这里总结可能有两点,第一是自己id的匹配错误,同一平台的硬件资源是有限的,每个摄像头插槽分配的资源也是固定的,即每个插槽的供电和gpio口是一定的,所以可以将设备树中的设备想象为对应的camera插槽,当你的sensor插在哪个插槽,xml文件中配置的camera id就要配置成相应设备中的cell-index,不然上电肯定会失败。第二个原因可能是上电时序配置错误,一般情况下硬件厂商会提供驱动代码,里面会有上电时序。

    2. 刷机失败

    在使用QFile刷机时,会报如下错误:


    qfile error

    原因是在编译过程中修改了源码,导致虽然编译成功,但是vbmeta.img是0字节,进而导致烧写失败,解决方案:重新编译源码。

    相关文章

      网友评论

          本文标题:第一个摄像头驱动移植

          本文链接:https://www.haomeiwen.com/subject/expsyctx.html