前言
OTP加載是攝像頭驅(qū)動(dòng)開(kāi)發(fā)中必不可少的一部分,初學(xué)者可能會(huì)覺(jué)得這一概念晦澀難懂,或者不知道其具體用途,本章節(jié)作為初學(xué)者的入門(mén)寶典,會(huì)介紹OTP的大致加載流程,并剖析源碼,分析其原理。
OTP的概念
OTP(One Time Programmable)一次性可編程,是MCU的一種存儲(chǔ)類型。目前還不太清楚為什么將OTP和eeprom混為一談,因?yàn)镺TP是一次性不可擦除,而eeprom是帶電可擦除存儲(chǔ)單元。所以目前簡(jiǎn)單認(rèn)為otp即eeprom,eeprom是一個(gè)存儲(chǔ)單元,用于存儲(chǔ)OTP數(shù)據(jù),OTP數(shù)據(jù)主要包括AF(馬達(dá)對(duì)焦)、AWB(自動(dòng)白平衡)、LSC(鏡頭陰影校正)和PDAF(相位對(duì)焦)等。具體燒寫(xiě)了哪些數(shù)據(jù)一般otp datasheet中會(huì)有說(shuō)明,比如說(shuō)AWB數(shù)據(jù)一般會(huì)有R/Gr,B/Gb,Gr/Gb,多的還會(huì)有g(shù)olden值。最終這些數(shù)據(jù)會(huì)被使用作為白平衡校準(zhǔn)的參數(shù)。那既然如此,我們直接在代碼中寫(xiě)死該參數(shù),不是同樣可以白平衡校準(zhǔn)嗎?答案是不行的。因?yàn)槊恳粋€(gè)模組從模組廠生產(chǎn)出來(lái)就一定會(huì)存在一些差異,所以用同樣的參數(shù)調(diào)試出來(lái)的結(jié)果效果會(huì)有較大的差異,會(huì)避免這種差異性,就需要“因材施教”,對(duì)每一個(gè)模組燒寫(xiě)合適的OTP數(shù)據(jù)。
OTP加載流程
otp加載可以大致分為以下幾個(gè)步驟:
1)執(zhí)行probe函數(shù)
2)加載庫(kù)文件
3)對(duì)eeprom上電,讀取eeprom數(shù)據(jù),下電
4)應(yīng)用數(shù)據(jù)
一、 執(zhí)行probe函數(shù)
probe函數(shù)是在內(nèi)核空間執(zhí)行,實(shí)現(xiàn)在msm_eeprom.c中,以下為eeprom驅(qū)動(dòng)定義的地方,驅(qū)動(dòng)需要掛在在某一總線上,才能和設(shè)備相匹配,一般內(nèi)核代碼中spi,i2c和platform總線的驅(qū)動(dòng)都有注冊(cè),具體調(diào)用哪個(gè)probe函數(shù)需要看設(shè)備樹(shù)中的eeprom設(shè)備掛載在哪條總線上
static struct i2c_driver msm_eeprom_i2c_driver = {
.id_table = msm_eeprom_i2c_id,
.probe = msm_eeprom_i2c_probe,
.remove = __exit_p(msm_eeprom_i2c_remove),
.driver = {
.name = "qcom,eeprom",
.owner = THIS_MODULE,
.of_match_table = msm_eeprom_i2c_dt_match,
},
};
static const struct of_device_id msm_eeprom_i2c_dt_match[] = {
{.compatible = "qcom,eeprom"},
{}
};
接下來(lái)分析設(shè)備樹(shù)文件:
&i2c_2 {
eeprom0: qcom,eeprom@0 {
cell-index = <0>; // subdev_id,配置成相應(yīng)的sensor的cell-index就可以了
reg = <0x5A>; //注冊(cè)寄存器,配置為i2c地址即可
qcom,eeprom-name = "sunwin_s5k4h7"; //eeprom名稱,加載庫(kù)文件時(shí)需要用到
compatible = "qcom,eeprom"; //設(shè)備與驅(qū)動(dòng)匹配的標(biāo)識(shí)
qcom,slave-addr = <0x5A>; //i2c通信地址
qcom,cci-master = <0>; //0
qcom,num-blocks = <6>; //以下讀寫(xiě)規(guī)則的步驟
page0 = <1 0x0A02 2 21 1 1>;
qcom,pageen0 = <0 0x0 0 0x0 0 0>;
qcom,poll0 = <0 0x0 0 0x0 0 0>;
qcom,mem0 = <0 0x0 2 0 1 1>;
qcom,page1 = <1 0x0A00 2 0x01 1 1>;
qcom,pageen1 = <0 0x0 0 0x0 0 0>;
qcom,poll1 = <0 0x0 0 0x0 0 0>;
qcom,mem1 = <43 0x0A04 2 0x0 1 10>;
qcom,page2 = <1 0x0A00 2 0x00 1 1>;
qcom,pageen2 = <0 0x0 0 0x0 0 0>;
qcom,poll2 = <0 0x0 0 0x0 0 0>;
qcom,mem2 = <0 0x0 2 0 1 1>;
qcom,page3 = <1 0x0A02 2 21 1 1>;
qcom,pageen3 = <0 0x0 0 0x0 0 0>;
qcom,poll3 = <0 0x0 0 0x0 0 0>;
qcom,mem3 = <0 0x0 2 0 1 1>;
qcom,page4 = <1 0x0A00 2 0x01 1 1>;
qcom,pageen4 = <0 0x0 0 0x0 0 0>;
qcom,poll4 = <0 0x0 0 0x0 0 0>;
qcom,mem4 = <12 0x0A30 2 0x0 1 1>;
qcom,page5 = <1 0x0A00 2 0x00 1 1>;
qcom,pageen5 = <0 0x0 0 0x0 0 0>;
qcom,poll5 = <0 0x0 0 0x0 0 0>;
qcom,mem5 = <0 0x0 2 0 1 1>;
cam_vdig-supply = <&pm8916_s3>; // DVDD電路配置1.4V
cam_vio-supply = <&pm8916_l10>; // IOVDD電路配置1.8V
qcom,cam-vreg-name = "cam_vio","cam_vdig";
qcom,cam-vreg-type = <0 0>;
qcom,cam-vreg-min-voltage = <1800000 1400000>;
qcom,cam-vreg-max-voltage = <1800000 1400000>;
qcom,cam-vreg-op-mode = <80000 200000>;
qcom,i2c-freq-mode = <0>;
qcom,enable_pinctrl;
pinctrl-names = "cam_default", "cam_suspend";
pinctrl-0 = <&cam_sensor_mclk0_default &cam_sensor_rear_default>;
pinctrl-1 = <&cam_sensor_mclk0_sleep &cam_sensor_rear_sleep>;
gpios = <&msm_gpio 26 0>, //GPIO口配置
<&msm_gpio 29 0>,
<&msm_gpio 33 0>;
qcom,gpio-reset = <1>;
qcom,gpio-standby = <2>;
qcom,gpio-req-tbl-num = <0 1 2>;
qcom,gpio-req-tbl-flags = <1 0 0>;
qcom,gpio-req-tbl-label = "CAMIF_MCLK",
"CAM_RESET0",
"CAM_STANDBY";
qcom,cam-power-seq-type = //上電配置
"sensor_vreg","sensor_vreg","sensor_gpio", "sensor_gpio","sensor_clk";
qcom,cam-power-seq-val =
"cam_vdig",
"cam_vio",
"sensor_gpio_standby",
"sensor_gpio_reset",
"sensor_cam_mclk";
qcom,cam-power-seq-cfg-val = <1 1 1 1 24000000>;
qcom,cam-power-seq-delay = <10 10 10 10 5>;
clocks = <&clock_gcc clk_mclk0_clk_src>,
<&clock_gcc clk_gcc_camss_mclk0_clk>;
clock-names = "cam_src_clk", "cam_clk";
};
}
設(shè)備樹(shù)節(jié)點(diǎn)解析:
- eeprom0: qcom,eeprom@0
eeprom0設(shè)備名稱,該設(shè)備節(jié)點(diǎn)上一節(jié)點(diǎn)是i2c_2,說(shuō)明該設(shè)備是掛載在i2c總線上的。qcom,eeprom@0應(yīng)該是父節(jié)點(diǎn)下的子節(jié)點(diǎn)區(qū)別于其他子節(jié)點(diǎn)的標(biāo)識(shí)。如果需要將sensor與eeprom綁定只要在sensor設(shè)備節(jié)點(diǎn)中添加屬性qcom,eeprom-src = <&eeprom0>;即可。 - qcom,eeprom-name = "sunwin_s5k4h7"
高通平臺(tái)下是否設(shè)置該節(jié)點(diǎn)區(qū)別很大,具體可參考源碼:
static int msm_eeprom_i2c_probe(struct i2c_client *client, const struct i2c_device_id *id){
...
rc = of_property_read_string(of_node, "qcom,eeprom-name",
&eb_info->eeprom_name);
CDBG("%s qcom,eeprom-name %s, rc %d\n", __func__,
eb_info->eeprom_name, rc);
if (rc < 0) {
pr_err("%s failed %d\n", __func__, __LINE__);
e_ctrl->userspace_probe = 1;
}
}
在設(shè)置了eeprom-name值后,eeprom的上電、讀數(shù)據(jù)和下電操作都在probe函數(shù)中實(shí)現(xiàn),而假如沒(méi)有設(shè)置eeprom-name,這些操作則是由用戶空間發(fā)送CFG_EEPROM_INIT指令給驅(qū)動(dòng),才執(zhí)行這些操作。同時(shí),qcom,eeprom-name屬性也要與相應(yīng)的庫(kù)文件名稱相匹配,否則會(huì)導(dǎo)致無(wú)法加載庫(kù)文件。
-
qcom,num-blocks = <6>
讀寫(xiě)規(guī)則部分需要參考o(jì)tp datasheet,代碼中我配置的是在0x0A04 讀取43個(gè)字節(jié),0x0A30 讀取12個(gè)字節(jié)。
image.png
相關(guān)的操作可以查看msm_eeprom_parse_memory_map函數(shù)實(shí)現(xiàn)
- gpios 和qcom,cam-power-seq-type
gpios配置了eeprom需要用到的gpio口,qcom,cam-power-seq-type配置供電電路。具體配置成什么得問(wèn)下硬件你的開(kāi)發(fā)板是燒成什么樣的。至于為什么需要配置這些,很簡(jiǎn)單,因?yàn)槟愕哪=M是外掛在主板上的,每個(gè)模組對(duì)應(yīng)一個(gè)sensor芯片,你的otp數(shù)據(jù)有可能直接燒寫(xiě)在sensor寄存器中,另一種是在sensor上外掛一個(gè)eeprom,但是總的來(lái)說(shuō),芯片只有一個(gè),那就是sensor芯片,假如需要讀取otp數(shù)據(jù),得讓sensor芯片先工作,然后才能讀相關(guān)的寄存器,配置相應(yīng)的gpio口和電路都是為了讓sensor芯片上電成功,這也就解釋了為什么sensor上電跟eeprom上電一樣,I2c通信地址也是一樣,因?yàn)檫@倆上電就是一個(gè)玩意兒,只是eeprom上電不需要AF供電而已。
叨嘮這么多,其實(shí)probe的執(zhí)行只需要看設(shè)備樹(shù)中的compatible 屬性是否一致,即“qcom,eeprom”。一般來(lái)說(shuō)probe函數(shù)會(huì)解析設(shè)備樹(shù)中節(jié)點(diǎn)數(shù)據(jù),將其存儲(chǔ)在e_crtl結(jié)構(gòu)體中,最終還將創(chuàng)建相應(yīng)的設(shè)備節(jié)點(diǎn)。eeprom的probe函數(shù)中是注冊(cè)了一個(gè)v4l2子設(shè)備,msm_sd_register即是注冊(cè)函數(shù),同時(shí)還將子設(shè)備與media device綁定,這樣便可以通過(guò)media設(shè)備遍歷找到eeprom設(shè)備的文件描述符,即是得到操作eeprom的接口。

二、 加載庫(kù)文件
庫(kù)文件是由驅(qū)動(dòng)文件編譯得到的,講到這兒就不得不說(shuō)一下驅(qū)動(dòng)程序,一般而言驅(qū)動(dòng)是操作系統(tǒng)與硬件的中間橋梁,驅(qū)動(dòng)作為一個(gè)硬件的訪問(wèn)接口而存在,而且一般運(yùn)行在內(nèi)核中,所以一般上層需要訪問(wèn)硬件,只需要往kernel發(fā)送幾條IOCTL指令就行了,但是在攝像頭的驅(qū)動(dòng)架構(gòu)中,比這要復(fù)雜得多,在高通的架構(gòu)中,就eeprom而言,內(nèi)核中有一個(gè)公共的驅(qū)動(dòng)程序msm_eeprom.c,用于與硬件打交道,而在mm-camera同樣需要加載一個(gè)庫(kù)文件,由供應(yīng)商提供的驅(qū)動(dòng)函數(shù)編譯而來(lái),此時(shí)的驅(qū)動(dòng)與設(shè)備不再是一個(gè)一一對(duì)應(yīng)的關(guān)系??梢钥闯鲈O(shè)備樹(shù)與驅(qū)動(dòng)的分離是為了解決不同開(kāi)發(fā)板的硬件差異問(wèn)題,vendor驅(qū)動(dòng)和kernel驅(qū)動(dòng)的分離則是為了解決不同供應(yīng)商的硬件訪問(wèn)差異性(eeprom的上電時(shí)序不同,讀寫(xiě)規(guī)則不同等)。
話不多說(shuō),入正文
int32_t eeprom_load_library(sensor_eeprom_data_t *e_ctrl)
{
...
snprintf(lib_name, sizeof(lib_name), "libmmcamera_%s_eeprom.so", name); //libmmcamera_%s_eeprom.so便是eeprom庫(kù)的命名方式
e_ctrl->eeprom_lib.eeprom_lib_handle = dlopen(lib_name, RTLD_NOW);
if (!e_ctrl->eeprom_lib.eeprom_lib_handle) {
return -EINVAL;
}
}
Android.mk
LOCAL_MODULE := libmmcamera_sunwin_s5k4h7_eeprom
三、對(duì)eeprom上電,讀取eeprom數(shù)據(jù),下電
這里介紹msm_eeprom_i2c_probe中上電的情況:
···
static int msm_eeprom_i2c_probe(struct i2c_client *client, const struct i2c_device_id *id)
rc = of_property_read_u32(of_node, "cell-index", &cell_id);
e_ctrl->subdev_id = cell_id; //設(shè)置subdev_id
rc = of_property_read_string(of_node, "qcom,eeprom-name",&eb_info->eeprom_name); //讀取eeprom_name
rc = msm_eeprom_get_dt_data(e_ctrl); //讀取設(shè)備樹(shù)信息
if (e_ctrl->userspace_probe == 0) {
rc = msm_eeprom_parse_memory_map(of_node, &e_ctrl->cal_data);
if (rc < 0)
goto board_free;
rc = msm_camera_power_up(power_info, e_ctrl->eeprom_device_type,
&e_ctrl->i2c_client); //上電
if (rc) {
pr_err("failed rc %d\n", rc);
goto memdata_free;
}
rc = read_eeprom_memory(e_ctrl, &e_ctrl->cal_data); //讀數(shù)據(jù)
if (rc < 0) {
pr_err("%s read_eeprom_memory failed\n", __func__);
goto power_down;
}
rc = msm_camera_power_down(power_info,
e_ctrl->eeprom_device_type, &e_ctrl->i2c_client); //下電
}
···
之前說(shuō)過(guò),eeprom上電即是sensor上電,所以上電時(shí)序只需要參考sensor的上電時(shí)序

四、應(yīng)用數(shù)據(jù)
應(yīng)用數(shù)據(jù)也可以認(rèn)為是驗(yàn)證otp數(shù)據(jù)是否導(dǎo)通,通過(guò)read_eeprom_memory可以讀出eeprom的內(nèi)容,此時(shí)首先應(yīng)該與模組供應(yīng)商確認(rèn)otp數(shù)據(jù)燒寫(xiě)無(wú)誤,然后再驗(yàn)證驅(qū)動(dòng)的正確性。otp數(shù)據(jù)校準(zhǔn)分為sensor端校準(zhǔn)、平臺(tái)端校準(zhǔn)和自動(dòng)校準(zhǔn)。sensor端校準(zhǔn)是AP先把OTP或者EEPROM中的數(shù)據(jù)讀出來(lái),然后由AP把相應(yīng)的值經(jīng)過(guò)轉(zhuǎn)換后寫(xiě)到sensor寄存器中去,平臺(tái)端校準(zhǔn)是AP讀出數(shù)據(jù)之后不寫(xiě)sensor寄存器,而是把這些值利用到ISP的一些數(shù)據(jù)里面去。自動(dòng)校準(zhǔn)是sensor自己可以自動(dòng)加載OTP的數(shù)據(jù),不需AP做處理。
- sensor端校準(zhǔn)
static eeprom_lib_func_t sunwin_s5k4h7_lib_func_ptr = {
.get_calibration_items = sunwin_s5k4h7_get_calibration_items,
.format_calibration_data = sunwin_s5k4h7_format_calibration_data,
.do_af_calibration = NULL, //設(shè)置為空
.do_wbc_calibration = NULL, //設(shè)置為空
.do_lsc_calibration = NULL, //設(shè)置為空
.get_raw_data = sunwin_s5k4h7_get_raw_data,
};
struct msm_camera_i2c_reg_setting g_reg_setting;
void sunwin_s5k4h7_get_calibration_items(void *e_ctrl)
{
sensor_eeprom_data_t *ectrl = (sensor_eeprom_data_t *)e_ctrl;
eeprom_calib_items_t *e_items = &(ectrl->eeprom_data.items);
e_items->is_insensor = TRUE;
e_items->is_afc = FALSE;
e_items->is_wbc = FALSE;
e_items->is_lsc = FALSE;
e_items->is_dpc = FALSE;
}
sensor端校準(zhǔn)將g_reg_setting寄存器組填充,該數(shù)組存儲(chǔ)所有需要寫(xiě)入sensor寄存器的地址和數(shù)據(jù),vendor調(diào)用get_raw_data 獲取該數(shù)組,然后將數(shù)據(jù)寫(xiě)入sensor。
- 平臺(tái)端校準(zhǔn)
static eeprom_lib_func_t ov13b10_eeprom_lib_func_ptr = {
.get_calibration_items = ov13b10_eeprom_get_calibration_items,
.format_calibration_data = ov13b10_eeprom_format_calibration_data,
.do_af_calibration = eeprom_autofocus_calibration,
.do_wbc_calibration = eeprom_whitebalance_calibration,
.do_lsc_calibration = eeprom_lensshading_calibration,
.get_raw_data = NULL,
.get_ois_raw_data = NULL,
}
void ov13b10_eeprom_get_calibration_items(void *e_ctrl)
{
sensor_eeprom_data_t *ectrl = (sensor_eeprom_data_t *)e_ctrl;
eeprom_calib_items_t *e_items = &(ectrl->eeprom_data.items);
e_items->is_wbc = TRUE ;
e_items->is_afc = TRUE ;
e_items->is_lsc = TRUE;
e_items->is_dpc = FALSE;
e_items->is_insensor = FALSE;
e_items->is_ois = FALSE;
}
平臺(tái)端校準(zhǔn)直接利用otp數(shù)據(jù)進(jìn)行補(bǔ)償計(jì)算,執(zhí)行校準(zhǔn)操作。
- sensor自動(dòng)校準(zhǔn)
目前還沒(méi)有接觸過(guò)。
