第一个摄像头驱动移植

2019-09-26  本文已影响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字节,进而导致烧写失败,解决方案:重新编译源码。

上一篇下一篇

猜你喜欢

热点阅读