操作步驟
1. 設(shè)備樹文件的修改
新平臺(tái)sdm429的設(shè)備樹文件路徑為kernel/msm-4.9/arch/arm64/boot/dts/qcom/,因?yàn)樾薷牡氖菙z像頭設(shè)備信息,修改文件為sdm429w-camera-sensor-spyro.dtsi
qcom,camera@1 {
cell-index = <1>; //sensor標(biāo)識(shí)
compatible = "qcom,camera"; //設(shè)備與驅(qū)動(dòng)相匹配的屬性
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>; //馬達(dá)供電
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. 驅(qū)動(dòng)文件和效果文件添加
在vendor/qcom/proprietary/mm-camera/mm-camera2/mediacontroller/modules/sensors/sensor/libs目錄下新建s5k4h7目錄谆刨,在該目錄下添加s5k4h7驅(qū)動(dòng)文件,并編寫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,添加新增的模塊血柳。
4. 修改msm8937_camera.xml
在vendor/qcom/proprietary/mm-camera/mm-camera2/media-controller/modules/sensors/configs/msm8937_camera.xml文件中添加如下CameraModuleConfig節(jié)點(diǎn):
<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>
原理分析
驅(qū)動(dòng)移植雖然步驟不多官册,但是麻煩卻一大堆,搞清楚其原理是一件很重要的事情难捌。這里首先梳理下攝像頭的初始化流程:在內(nèi)核初始化過程中會(huì)將qcom,camera@1節(jié)點(diǎn)轉(zhuǎn)換為platform_device,當(dāng)設(shè)備和驅(qū)動(dòng)匹配會(huì)調(diào)用驅(qū)動(dòng)的probe函數(shù)膝宁,該函數(shù)讀取設(shè)備節(jié)點(diǎn)信息并保存。另外根吁,Qualcomm的camera守護(hù)進(jìn)程端獲取需要初始化的sensor员淫,然后加載其驅(qū)動(dòng)程序,并將驅(qū)動(dòng)信息傳遞給內(nèi)核击敌,內(nèi)核對其上電測試成功后創(chuàng)建/dev/video1設(shè)備文件節(jié)點(diǎn)介返。可以看出其最終目的還是創(chuàng)建一個(gè)設(shè)備文件節(jié)點(diǎn)給上層訪問沃斤。
這里以sensor_init.c作為切入點(diǎn)圣蝎,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數(shù)量
for (i = 0; i < num_cam_config; i++) {
nodePtr = sensor_xml_util_get_node(rootPtr, "CameraModuleConfig", i); //獲取第i個(gè)CameraModuleConfig節(jié)點(diǎn)
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);
...
}
可以看出該函數(shù)是遍歷xml文件中的CameraModuleConfig節(jié)點(diǎn),解析節(jié)點(diǎn)信息衡瓶,至于遍歷的是哪個(gè)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;
} //發(fā)送probe指令給sensor_init
...
}
sensor_load_library主要是加載sensor的驅(qū)動(dòng)信息关面,通過sensor name加載對應(yīng)的.so文件,然后執(zhí)行sensor_open_lib函數(shù)十厢,獲取sensor的各項(xiàng)配置參數(shù)等太,查看s5k4h7的驅(qū)動(dòng)程序,該函數(shù)返回一個(gè)sensor_lib_t結(jié)構(gòu)體地址蛮放。然后將各參數(shù)傳入slave_info澈驼,傳給內(nèi)核。調(diào)用sensor_init設(shè)備的ioctl接口筛武,最終調(diào)用到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驅(qū)動(dòng)程序配置信息
...
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上電分為兩個(gè)步驟挎塌,第一步給sensor上電并初始化cci,第二步匹配id内边。因?yàn)槊總€(gè)sensor都是作為一個(gè)slave掛在cci總線上榴都,而sdm429則是作為一個(gè)master,他們通訊需要遵循cci協(xié)議漠其,所以只有sensor上電成功嘴高,sdm429才能與其通信,讀取其id和屎,而第二步id匹配則是比較slave_info中的id和讀取的id是否相同拴驮。
g_sctrl是一個(gè)全局變量,在platform_driver的probe函數(shù)中填充柴信,這是一個(gè)很重要的結(jié)構(gòu)體指針數(shù)組套啤,包含了所有sensor的所有信息,也就是說設(shè)備樹中有幾個(gè)qcom,camera@1節(jié)點(diǎn)随常,g_sctrl就有幾組數(shù)據(jù)潜沦,然后我們通過camera id索引得到對應(yīng)的msm_sensor_ctrl_t結(jié)構(gòu)體,該結(jié)構(gòu)體的成員變量sensordata包含了camera的板級信息(供電绪氛、io口)唆鸡,對應(yīng)著硬件資源,我們在probe的時(shí)候需要申請這些資源枣察,不然cpu也不知道如何給sensor供電了争占。那現(xiàn)在最關(guān)鍵的就是查看g_sctrl在哪里填充的了,閱讀代碼可以看出序目,每匹配到一個(gè)camera設(shè)備臂痕,msm_sensor_driver_platform_probe執(zhí)行一次,并申請一個(gè)msm_sensor_ctrl_t結(jié)構(gòu)體:
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); //獲取設(shè)備樹節(jié)點(diǎn)屬性
...
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的填充索引值為設(shè)備樹中的cell-index宛琅,獲取該數(shù)組成員的索引值為sensor驅(qū)動(dòng)的slave_info->camera_id刻蟹,由xml文件讀取得到。
作為一個(gè)小白嘿辟,我開始只寫過裸板驅(qū)動(dòng)舆瘪,并不清楚驅(qū)動(dòng)、設(shè)備跟總線的關(guān)系红伦,所以一開始直接上手設(shè)備樹是很懵的英古,一開始并不明白寫了xxx_driver為什么還要寫xxx_device,后來在學(xué)習(xí)platform總線的時(shí)候又一直在找platform_device在哪里定義昙读,所以在此強(qiáng)烈建議小白學(xué)習(xí)一下設(shè)備驅(qū)動(dòng)模型和linux設(shè)備樹
問題總結(jié)
1. read id failed
在日志文件中搜索關(guān)鍵字match_id可以得到如下信息
讀取id失敗一般情況下是由于上電失敗導(dǎo)致的召调,上電不成功,sensor是無法進(jìn)行cci通信的,上電失敗的原因這里總結(jié)可能有兩點(diǎn)唠叛,第一是自己id的匹配錯(cuò)誤只嚣,同一平臺(tái)的硬件資源是有限的,每個(gè)攝像頭插槽分配的資源也是固定的艺沼,即每個(gè)插槽的供電和gpio口是一定的册舞,所以可以將設(shè)備樹中的設(shè)備想象為對應(yīng)的camera插槽,當(dāng)你的sensor插在哪個(gè)插槽障般,xml文件中配置的camera id就要配置成相應(yīng)設(shè)備中的cell-index调鲸,不然上電肯定會(huì)失敗。第二個(gè)原因可能是上電時(shí)序配置錯(cuò)誤挽荡,一般情況下硬件廠商會(huì)提供驅(qū)動(dòng)代碼藐石,里面會(huì)有上電時(shí)序。
2. 刷機(jī)失敗
在使用QFile刷機(jī)時(shí)定拟,會(huì)報(bào)如下錯(cuò)誤:
原因是在編譯過程中修改了源碼于微,導(dǎo)致雖然編譯成功,但是vbmeta.img是0字節(jié)办素,進(jìn)而導(dǎo)致燒寫失敗角雷,解決方案:重新編譯源碼祸穷。