【Camera專題】Qcom-高通OTP完全除錯指南-上

c楓_擼碼的日子發表於2019-03-02

一、前言

關於高通OTP程式設計的知識,網上少得可憐,官方文件又沒有那麼清晰,於是就來一篇乾貨吧! OTP程式設計完全指南分上、下2篇。 上:主要講OTP的知識和除錯流程。 下:主要講OTP的原始碼。

本文知識點:

  • 1.OTP的基本概念
  • 2.OTP的作用
  • 3.OTP的除錯流程

二、知識點

1.OTP的基本概念(是什麼)

OTP(One Time Programmable)意思是一次性可程式設計,程式或者資料燒入【儲存器】後,將不可再次更改和清除。


OTP燒錄的資料型別 一般包括:

  • AF:自動對焦校準資料
  • AWB:白平衡校準資料
  • LSC:鏡頭陰影校準 (Lens Shading Calibration)
  • Moudle Info:模組資訊,包含模組的生產年日月,模組ID等

【Camera專題】Qcom-高通OTP完全除錯指南-上
以AF為例子:
AF資料
vendor廠燒錄的AF資料:

Page:3,Addr:0x01D0,Data:0x00
Page:3,Addr:0x01D8,Data:0x04
Page:3,Addr:0x01E0,Data:0x0F
Page:3,Addr:0x01E8,Data:0x0C
Page:3,Addr:0x01F0,Data:0x02
Page:3,Addr:0x01F8,Data:0x00
複製程式碼

OTP儲存器的型別 按照除錯的經驗,目前主流的有2種:

  • 1.OTP資料燒錄在sensor的暫存器中。 這種方案省錢,不需要額外的儲存器件,但是儲存空間小,如果需要燒錄的資料量過大,就不適用。

  • OTP資料燒錄在EEPROM 中: EEPROM(Electrically Erasable Programmable read only memory)是指帶電可擦可程式設計只讀儲存器, 是一種掉電後資料不丟失的儲存晶片。 該方案優勢是儲存空間大,如果資料量過多,就需要這種方案,缺點是多一個獨立的EEPROM儲存器件, 花點錢(5毛錢左右)。

2.OTP的作用(為什麼)

OTP是用來給camera sensor做calibration(校準)用的。 因為模組生產出來會有很大的差異性,為了保證效果一致性, 模組廠會挑選一部分模組作為golden,然後將其他模組的相應引數校準到和這些golden一樣, (golden不是最好的模組,也不是最差的模組,而是各方面最平均的模組)。

3.OTP除錯流程(怎麼做)

除錯平臺:8909(較為低端)

PS:在高通原始碼的OTP指的就是EEPROM驅動。

【Camera專題】Qcom-高通OTP完全除錯指南-上

例子一:以OV5675為例子(資料燒錄在Camera Sensor中)

3.1 OTP除錯準備工作

  • 從datasheet獲取相關資訊 OTP Datasheet(OV5675 Calibration and OTP Programming Guid) Camera sensor Datasheet(sensor_OV05675-GA4A.pdf) a.弄明白上電時序(這個和camera上電時序是一致的)
    上電時序

b.獲得slave address

I2C地址
硬體上我們這個pin腳是拉高的,所以I2C addr = 0x20

c.弄清楚讀寫規則

讀寫規則-描述版
讀寫規則-程式碼版

d.其他

1. 供電:cam_vio-supply = <&pm8916_l10>;
2. clock:
   clocks = <&clock_gcc clk_mclk0_clk_src>,<&clock_gcc clk_gcc_camss_mclk0_clk>;
   clock-names = "cam_src_clk", "cam_clk";
3.GPIO pins
  gpios = 
        <&msm_gpio 26 0>,
        <&msm_gpio 28 0>,
        <&msm_gpio 33 0>;
複製程式碼

3.2 配置 DTSI檔案

EEPROM資料在裝置啟動時讀取。需要將記憶體對映轉換為dtsi中的對應的屬性節點。 其中必須指定調節器(供電)、時鐘訊號、電源啟動序列、裝置地址和讀取序列。 路徑:kernel/arch/arm/boot/dts/qcom/msm8909-pm8916-camera-sensor-i18.dtsi

eeprom1: qcom,eeprom@20 {
        cell-index = <1>;/*分配給eeprom subdev,唯一即可*/
        reg = <0x20>;/*註冊地址*/
        qcom,eeprom-name = "ov5675_back";/*eeprom驅動名稱,必須與驅動力的名稱一致*/
        compatible = "qcom,eeprom";/*匹配節點,都是這個值*/
        qcom,slave-addr = <0x20>;/*i2c地址*/
        qcom,cci-master = <0>;/*預設都為0即可*/
        qcom,num-blocks = <10>;/*下面配置的page個數*/

        /*讀寫規則*/
        qcom,page0 = <1 0x0100 2 0x01 1 10>;/*steam on 該操作非必須*/
        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 0x5001 2 0x02 1 1>;/*往0x5001寫0x02:OTP enable*/
        qcom,pageen1 = <0 0x0 0 0x0 0 0>;
        qcom,poll1 = <0 0x0 0 0x0 0 0>;
        qcom,mem1 = <0 0x5001 2 0 1 1>;

        qcom,page2 = <1 0x3d84 2 0xc0 1 1>;/*往0x3d84寫入0xc0:Enable partial OTP write */
        qcom,pageen2 = <0 0x0 2 0x0 0 0>;
        qcom,poll2 = <0 0x0 2 0x0 0 0>;
        qcom,mem2 = <0 0x0 2 0 0 0>;

        qcom,page3 = <1 0x3d88 2 0x70 1 1>;/*往0x3d88寫入0x70:start address 高8位地址*/
        qcom,pageen3 = <0 0x0 2 0x0 1 1>;
        qcom,poll3 = <0 0x0 2 0x0 0 0>;
        qcom,mem3 = <0 0x0 2 0 0 0>;

        qcom,page4 = <1 0x3d89 2 0x10 1 1>;/*往0x3d88寫入0x10:start address 低8位地址*/
        qcom,pageen4 = <0 0x0 2 0x0 1 1>;
        qcom,poll4 = <0 0x0 2 0x0 0 0>;
        qcom,mem4 = <0 0x0 2 0 0 0>;

        qcom,page5 = <1 0x3d8a 2 0x72 1 1>;/*往0x3d8a寫入0x72:end address 高8位地址*/
        qcom,pageen5 = <0 0x0 2 0x0 1 1>;
        qcom,poll5 = <0 0x0 2 0x0 0 0>;
        qcom,mem5 = <0 0x0 2 0 0 0>;

        qcom,page6 = <1 0x3d8b 2 0x29 1 1>;/*往0x3d8b寫入0x29:end address 低8位地址*/
        qcom,pageen6 = <0 0x0 2 0x0 1 1>;
        qcom,poll6 = <0 0x0 2 0x0 0 0>;
        qcom,mem6 = <0 0x0 2 0 0 0>;

        qcom,page7 = <1 0x3d81 2 0x01 1 10>;/*往0x3d81寫入0x01:把OTP資料載入到buffer中 */
        qcom,pageen7 = <0 0x0 0 0x0 0 0>;
        qcom,poll7 = <0 0x0 0 0x0 0 0>;
        qcom,mem7 = <256 0x7010 2 0 1 1>;/*從0x7010開始讀取256個資料*/

        qcom,page8 = <1 0x5001 2 0x0a 1 1>;/*往0x5001寫0x0a:OTP disable*/
        qcom,pageen8 = <0 0x0 0 0x0 0 0>;
        qcom,poll8 = <0 0x0 0 0x0 0 0>;
        qcom,mem8 = <0 0x0 2 0 1 1>;

        qcom,page9 = <1 0x0100 2 0x00 1 10>;/*steam off*/
        qcom,pageen9 = <0 0x0 0 0x0 0 0>;
        qcom,poll9 = <0 0x0 0 0x0 0 0>;
        qcom,mem9 = <0 0x0 2 0 1 1>;

        cam_vio-supply = <&pm8916_l10>;/*供電相關:和camera一致即可*/
        qcom,cam-vreg-name = "cam_vio";;/*硬體上只需IO供電,其他AVDD和DVDD都會被IO拉起來*/
        qcom,cam-vreg-type = <0>;
        qcom,cam-vreg-min-voltage = <1800000>;
        qcom,cam-vreg-max-voltage = <2800000>;
        qcom,cam-vreg-op-mode = <80000>;
        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 = <&msm_gpio 26 0>,/*GPIO相關:和camera一致即可*/
        <&msm_gpio 28 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_RESET1",
        "CAM_STANDBY";
        qcom,cam-power-seq-type =/*eeprom的上電時序:和camera sensor的一致*/
        "sensor_vreg","sensor_gpio", "sensor_gpio","sensor_clk";
        qcom,cam-power-seq-val =
        "cam_vio",
        "sensor_gpio_standby",
        "sensor_gpio_reset",
        "sensor_cam_mclk";
        qcom,cam-power-seq-cfg-val = <1 1 1 24000000>;
        qcom,cam-power-seq-delay = <10 10 10 5>;

        clocks = <&clock_gcc clk_mclk0_clk_src>,/*clock:和camera一致即可*/
        <&clock_gcc clk_gcc_camss_mclk0_clk>;
        clock-names = "cam_src_clk", "cam_clk";
    };
複製程式碼
qcom,camera@1 {//在camera中應用eeprom1
···
        qcom,eeprom-src = <&eeprom1>;
···
}
複製程式碼

屬性節點含義

  • cell-index = <1>; 該節點用於eeprom subdev註冊subdev_id,唯一即可!
  • reg = <0x20> 註冊地址:高階平臺要求這個地址唯一即可,低端平臺藉助這個地址和i2c通訊, 保險起見,統一設定為i2c地址。
  • qcom,eeprom-name = "ov5675_back"; 這個名稱必須和eeprom驅動的名稱一致,例如
vendor/qcom/proprietary/mm-camera/mm-camera2/media-controller/
modules/sensors/sensor_libs/ov5675_back/ov5675_back_lib.c
static sensor_lib_t sensor_lib_ptr = { 
  /* sensor eeprom name */
  .eeprom_name = "ov5675_back",
}
```c

* qcom,slave-addr = <0x20>;
  I2C 裝置地址
* cam_vio-supply = <&pm8916_l10>;
  供電電源
* qcom,cam-power-seq-type
上電時序
```c
上電的型別
qcom,cam-power-seq-type = "sensor_vreg","sensor_gpio", "sensor_gpio","sensor_clk";
上電型別的對應的val
qcom,cam-power-seq-val = "cam_vio","sensor_gpio_standby","sensor_gpio_reset","sensor_cam_mclk";
上電時序的值:除了clock配置成相應的值,其他全配置1
qcom,cam-power-seq-cfg-val = <1 1 1 24000000>;
上電延遲時間
qcom,cam-power-seq-delay = <10 10 10 5>;
複製程式碼

事實上,這個上電時序跟Camera Sensor的上電時序是一致的!舉個例子

【Camera專題】Qcom-高通OTP完全除錯指南-上

  • qcom,page0 = = <有效值 地址 地址型別 資料 資料型別 延遲> 地址型別:1代表1 byte ,2代表2byte = 1 word 資料型別:1代表1 byte ,2代表2byte = 1 word 讀寫規則
qcom,page7 = <1 0x3d81 2 0x01 1 10>;/*往0x3d81寫入0x01:把OTP資料載入到buffer中 */
qcom,pageen7 = <0 0x0 0 0x0 0 0>;
qcom,poll7 = <0 0x0 0 0x0 0 0>;
qcom,mem7 = <256 0x7010 2 0 1 1>;/*從0x7010開始讀取256個資料*/
複製程式碼

【Camera專題】Qcom-高通OTP完全除錯指南-上
到此,dtsi的配置就完成了!!!

3.3 軟體驅動配置

1.新增新的EEPROM驅動

vendor/qcom/proprietary/mm-camera/mm-camera2/media-controller/modules/
sensors/eeprom_libs/ov5675_back
* ov5675_back.c
* Android.mk
複製程式碼

任何新 .c 檔案都應對映和定義以下函式指標。 所有未在此 EEPROM 驅動程 序中定義的函式必須設定為 NULL。

【Camera專題】Qcom-高通OTP完全除錯指南-上
.get_calibration_items() – 此函式應返回 EEPROM 模組所支援的配置。 基於 EEPROM 所支援的配置,將指定標記設定為 TRUE 或 FALSE。
【Camera專題】Qcom-高通OTP完全除錯指南-上

  • Is_insensor – 如果感測器模組本身支援 EEPROM 配置,則將此標記設定為 TRUE。 外部 EEPROM 均不可用。
  • Is_afc – 如果支援 AF 校準,將此標記設定為 TRUE。
  • Is_wbc – 如果支援白平衡校準,將此標記設定為 TRUE。
  • Is_lsc – 如果支援鏡頭陰影校準,將此標記設定為 TRUE。
  • Is_dpc – 如果支援缺陷畫素校正,將此標記設定為 TRUE

.format_calibration_data() – 此函式用於格式化可寫入 eeprom/ 感測器模組的資料

【Camera專題】Qcom-高通OTP完全除錯指南-上
OTP資料應用

  • .do_af_calibration() – 此函式用於處理所有與 AF 相關的校準操作, 如格式化資料和將 其寫入 EEPROM 以執行 AF 校準。
  • .do_wbc_calibration() – 此函式用於處理所有與白平衡相關的校準操作, 如格式化資料 和將其寫入 EEPROM 以執行白平衡校準。
  • .do_lsc_calibration() – 此函式用於處理所有與鏡頭陰影校正相關的校準操作, 如格式化 資料和將其寫入 EEPROM 以執行鏡頭陰影校準。
  • .do_dpc_calibration() – 此函式用於處理所有與缺陷畫素校正相關的校準操作, 如格式化 資料和將其寫入 EEPROM 以執行缺陷畫素校正

例子二:以獨立EEPROM為例子(資料燒錄在獨立的EEPROM中)

步驟和例子1是一樣的,關鍵在於dtsi的配置

eeprom0: qcom,eeprom@a0 { 
        cell-index = <0>; 
        reg = <0xa0>; 
        qcom,eeprom-name = "gc8034_otp"; 
        compatible = "qcom,eeprom"; 
        qcom,slave-addr = <0xa0>; 
        qcom,cci-master = <0>; 
        qcom,num-blocks = <1>; 
        qcom,page0 = <0 0 0 0 0 0>; 
        qcom,pageen0 = <0 0x0 0 0x0 0 0>; 
        qcom,poll0 = <0 0x0 0 0x0 0 0>; 
        qcom,mem0 = <1813 0x0000 2 0 1 1>; 
        cam_vio-supply = <&pm8916_l10>; 
        qcom,cam-vreg-name = "cam_vio"; 
        qcom,cam-vreg-type = <0>; 
        qcom,cam-vreg-min-voltage = <1800000>; 
        qcom,cam-vreg-max-voltage = <2800000>; 
        qcom,cam-vreg-op-mode = <80000>;
        qcom,cam-power-seq-type = "sensor_vreg"; 
        qcom,cam-power-seq-val ="cam_vio"; 
        qcom,cam-power-seq-cfg-val = <1>; 
        qcom,cam-power-seq-delay = <10>; 
    }; 
複製程式碼

最關鍵的地方就是reg = <0xa0>; 這裡要配置成I2C地址,讀取陣列的時候,I2C會自動把a0>>1=0x50去通訊! 當然高階點的平臺就不需要關注reg,只需配置唯一即可,最好的辦法還是配置為i2c地址!

8909平臺不支援reg配置成a0,核心中有效地址是0x00~0x7f直接,如果配置成a0, 會報錯:Invalid7-bit I2C address 0xa0!!! 因此需要修改一下核心: kernel/drivers/i2c/i2c-core.c

static int i2c_check_client_addr_validity(const struct i2c_client *client)
{
    
    if (client->flags & I2C_CLIENT_TEN) {
        /* 10-bit address, all values are valid */
        if (client->addr > 0x3ff)
            return -EINVAL;
    } else {
        if (client->addr == 0xa0)//讓a0地址合法化!!!
            return 0;
        /* 7-bit address, reject the general call address */
        if (client->addr == 0x00 || client->addr > 0x7f)
            return -EINVAL;
    }    
    return 0;
}

複製程式碼

供電這一塊,eeprom只需要IO供電即可:因此配置就更簡單了

        cam_vio-supply = <&pm8916_l10>; 
        qcom,cam-vreg-name = "cam_vio"; 
        qcom,cam-vreg-type = <0>; 
        qcom,cam-vreg-min-voltage = <1800000>; 
        qcom,cam-vreg-max-voltage = <2800000>; 
        qcom,cam-vreg-op-mode = <80000>;
        qcom,cam-power-seq-type = "sensor_vreg"; 
        qcom,cam-power-seq-val ="cam_vio"; 
        qcom,cam-power-seq-cfg-val = <1>; 
        qcom,cam-power-seq-delay = <10>; 
複製程式碼

讀寫規則:直接從0x00開始讀1813個資料,不需要操作任何暫存器!

qcom,mem0 = <1813 0x0000 2 0 1 1>; 
複製程式碼

例子三:以GC8034為例子(資料燒錄在Camera Sensor中)

GC8034的讀寫規則比較複雜,和高通要求的不一樣!

讀寫規則
高通的原始碼是給一個初始地址,然後不停+1的往後讀取資料,最後儲存在buffer中! GC8034是去讀d7這個暫存器的值!(這些讀寫規則,要多和模組廠跟sensor廠溝通) 因此要改動kernel層的原始碼

static int read_eeprom_memory(struct msm_eeprom_ctrl_t *e_ctrl,
    struct msm_eeprom_memory_block_t *block)
        if (emap[j].mem.valid_size) {
            /*   galaxycore start  */
            if(0 == strcmp(eb_info->eeprom_name,"gc8034_otp")){
                    e_ctrl->i2c_client.addr_type = 1;  /* luyi */
                    /*讀取0xf4到gc_readf4變數中*/
                    rc=e_ctrl->i2c_client.i2c_func_tbl->i2c_read(
                            &(e_ctrl->i2c_client), 0xf4, &gc_readf4, emap[j].mem.data_t);
                    /*往d4暫存器寫page和高8位地址*/
                    e_ctrl->i2c_client.i2c_func_tbl->i2c_write(
                            &(e_ctrl->i2c_client), 0xd4, (emap[j].mem.addr >> 8) & 0xff, emap[j].mem.data_t);
                    /*往d5暫存器寫低8位地址*/
                    e_ctrl->i2c_client.i2c_func_tbl->i2c_write(
                            &(e_ctrl->i2c_client), 0xd5, emap[j].mem.addr & 0xff, emap[j].mem.data_t);
                    /*往f3暫存器寫入0x20:OTP read 模式*/
                    e_ctrl->i2c_client.i2c_func_tbl->i2c_write(
                            &(e_ctrl->i2c_client), 0xf3, 0x20, emap[j].mem.data_t);
                    /*往f4暫存器的第2位置1,表示地址自動++(按照1 個byte=8bit的方式)*/
                    e_ctrl->i2c_client.i2c_func_tbl->i2c_write(
                            &(e_ctrl->i2c_client), 0xf4, gc_readf4 | 0x02, emap[j].mem.data_t);
                    /*往f3暫存器寫入80,設定自動讀取訊號*/
                    e_ctrl->i2c_client.i2c_func_tbl->i2c_write(
                            &(e_ctrl->i2c_client), 0xf3, 0x80, emap[j].mem.data_t);
                    msleep(emap[j].mem.delay);//延時
                    for(gc = 0; gc < emap[j].mem.valid_size; gc++){
                        msleep(emap[j].mem.delay);
                        rc=e_ctrl->i2c_client.i2c_func_tbl->i2c_read(//讀d7暫存器的值到gc_read變數中
                                &(e_ctrl->i2c_client), 0xd7, &gc_read, emap[j].mem.data_t);
                        if (rc < 0) {
                            pr_err("%s: read failed %d \n", __func__, __LINE__);
                            return rc;
                        }
                        *memptr = (uint8_t)gc_read;//把讀出來的值保持到memptr 中
                        memptr++;
                    }
                    e_ctrl->i2c_client.i2c_func_tbl->i2c_write(//讀完復位成初始狀態
                            &(e_ctrl->i2c_client), 0xf3, 0x00, emap[j].mem.data_t);
                    e_ctrl->i2c_client.i2c_func_tbl->i2c_write(//讀完復位成初始狀態
                            &(e_ctrl->i2c_client), 0xf4, gc_readf4 & 0xfd, emap[j].mem.data_t);
                }
                /*galaxycore end*/
            else{//高通平臺預設的讀取方式
                e_ctrl->i2c_client.addr_type = emap[j].mem.addr_t;
            rc = e_ctrl->i2c_client.i2c_func_tbl->i2c_read_seq(
                &(e_ctrl->i2c_client), emap[j].mem.addr,
                memptr, emap[j].mem.valid_size);
                pr_err("%s:travis read addr = %d,value = %d\n\n", __func__,emap[j].mem.addr,memptr[0]);
            if (rc < 0) {
                pr_err("%s: read failed\n", __func__);
                return rc;
            }
            memptr += emap[j].mem.valid_size;
            }
        }
}
複製程式碼

Stay Hungry,Stay Foolish!

相關文章