一、前言
關於高通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等
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驅動。
例子一:以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 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的上電時序是一致的!舉個例子
- 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個資料*/
複製程式碼
到此,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。
.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;
}
}
}
複製程式碼