第一个摄像头驱动移植
操作步骤
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字节,进而导致烧写失败,解决方案:重新编译源码。