【Camera专题】Qcom-高通OTP完全调试指南-上
一、前言
關于高通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等
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驅動。
例子一:以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
硬件上我們這個pin腳是拉高的,所以I2C addr = 0x20c.弄清楚讀寫規則
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 pinsgpios = <&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驅動的名稱一致,例如
事實上,這個上電時序跟Camera Sensor的上電時序是一致的!舉個例子
- qcom,page0 = = <有效值 地址 地址類型 數據 數據類型 延遲> 地址類型:1代表1 byte ,2代表2byte = 1 word 數據類型:1代表1 byte ,2代表2byte = 1 word 讀寫規則
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。
.get_calibration_items() – 此函數應返回 EEPROM 模塊所支持的配置。 基于 EEPROM 所支持的配置,將指定標記設置為 TRUE 或 FALSE。- Is_insensor – 如果傳感器模塊本身支持 EEPROM 配置,則將此標記設置為 TRUE。 外部 EEPROM 均不可用。
- Is_afc – 如果支持 AF 校準,將此標記設置為 TRUE。
- Is_wbc – 如果支持白平衡校準,將此標記設置為 TRUE。
- Is_lsc – 如果支持鏡頭陰影校準,將此標記設置為 TRUE。
- Is_dpc – 如果支持缺陷像素校正,將此標記設置為 TRUE
.format_calibration_data() – 此函數用于格式化可寫入 eeprom/ 傳感器模塊的數據
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!
轉載于:https://juejin.im/post/5c79d7cf6fb9a049fe35db4d
總結
以上是生活随笔為你收集整理的【Camera专题】Qcom-高通OTP完全调试指南-上的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: C++中全排列算法函数next_perm
- 下一篇: python实现单例模式