關於高通OTP編程的知識,網上少得可憐,官方文檔又沒有那麼清晰,因而就來一篇乾貨吧! OTP編程徹底指南分上、下2篇。 上:主要講OTP的知識和調試流程。 下:主要講OTP的源碼。ios
本文知識點:c++
OTP(One Time Programmable)意思是一次性可編程,程序或者數據燒入【存儲器】後,將不可再次更改和清除。編程
OTP燒錄的數據類型 通常包括:數組
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種:bash
1.OTP數據燒錄在sensor的寄存器中。 這種方案省錢,不須要額外的存儲器件,可是存儲空間小,若是須要燒錄的數據量過大,就不適用。函數
OTP數據燒錄在EEPROM 中: EEPROM(Electrically Erasable Programmable read only memory)是指帶電可擦可編程只讀存儲器, 是一種掉電後數據不丟失的存儲芯片。 該方案優點是存儲空間大,若是數據量過多,就須要這種方案,缺點是多一個獨立的EEPROM存儲器件, 花點錢(5毛錢左右)。ui
OTP是用來給camera sensor作calibration(校準)用的。 由於模組生產出來會有很大的差別性,爲了保證效果一致性, 模組廠會挑選一部分模組做爲golden,而後將其餘模組的相應參數校準到和這些golden同樣, (golden不是最好的模組,也不是最差的模組,而是各方面最平均的模組)。spa
調試平臺:8909(較爲低端)3d
PS:在高通源碼的OTP指的就是EEPROM驅動。 指針
b.得到slave address
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>;
複製代碼
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>;
···
}
複製代碼
屬性節點含義
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,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個數據*/
複製代碼
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。
.format_calibration_data() – 此函數用於格式化可寫入 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的讀寫規則比較複雜,和高通要求的不同!
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;
}
}
}
複製代碼