OTP入門(mén)指南

前言

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)解析:

  1. 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>;即可。
  2. 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ù)文件。

  1. 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)

  2. 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的接口。


image.png

二、 加載庫(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í)序


image.png

四、應(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做處理。

  1. 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。

  1. 平臺(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)操作。

  1. sensor自動(dòng)校準(zhǔn)
    目前還沒(méi)有接觸過(guò)。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容