深圳市科维通信技术有限公司论坛

 找回密码
 立即注册
搜索
热搜: 活动 交友 discuz
查看: 1045|回复: 4

Qcom-高通OTP编程调试指南-上

[复制链接]

7

主题

11

帖子

96

积分

版主

Rank: 7Rank: 7Rank: 7

积分
96
发表于 2020-7-28 14:54:27 | 显示全部楼层 |阅读模式
本帖最后由 Alpaca 于 2020-7-28 14:58 编辑

一、前言
本文基于高通8909平台
关于高通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等
  • pdaf,remosiac校准数据,ois校准数据等等(这种看sensor功能)

以AF为例子:


vendor厂烧录的AF数据:
  1. Page:3,Addr:0x01D0,Data:0x04
  2. Page:3,Addr:0x01D8,Data:0x01
  3. Page:3,Addr:0x01E0,Data:0xBE
  4. Page:3,Addr:0x01E8,Data:0x54
  5. Page:3,Addr:0x01F0,Data:0x15
  6. 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驱动。

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 = 0x20


c.弄清楚读写规则






d.其他

  1. 1. 供电:cam_vio-supply = <&pm8916_l10>;
  2. 2. clock:
  3.    clocks = <&clock_gcc clk_mclk0_clk_src>,<&clock_gcc clk_gcc_camss_mclk0_clk>;
  4.    clock-names = "cam_src_clk", "cam_clk";
  5. 3.GPIO pins
  6.   gpios =
  7.         <&msm_gpio 26 0>,
  8.         <&msm_gpio 28 0>,
  9.         <&msm_gpio 33 0>;
复制代码


3.2 配置 DTSI文件
EEPROM数据在设备启动时读取。需要将内存映射转换为dtsi中的对应的属性节点。
其中必须指定调节器(供电)、时钟信号、电源启动序列、设备地址和读取序列。
路径:kernel/arch/arm/boot/dts/qcom/msm8909-pm8916-camera-sensor.dtsi
  1. eeprom1: qcom,eeprom@20 {
  2.         cell-index = <1>;/*分配给eeprom subdev,唯一即可*/
  3.         reg = <0x20>;/*注册地址*/
  4.         qcom,eeprom-name = "ov5675_back";/*eeprom驱动名称,必须与驱动力的名称一致*/
  5.         compatible = "qcom,eeprom";/*匹配节点,都是这个值*/
  6.         qcom,slave-addr = <0x20>;/*i2c地址*/
  7.         qcom,cci-master = <0>;/*默认都为0即可*/
  8.         qcom,num-blocks = <10>;/*下面配置的page个数*/

  9.         /*读写规则*/
  10.         qcom,page0 = <1 0x0100 2 0x01 1 10>;/*steam on */
  11.         qcom,pageen0 = <0 0x0 0 0x0 0 0>;
  12.         qcom,poll0 = <0 0x0 0 0x0 0 0>;
  13.         qcom,mem0 = <0 0x0 2 0 1 1>;
  14.         /*初始化操作*/
  15.         qcom,page1 = <1 0x5001 2 0x02 1 1>;/*往0x5001写0x02:OTP enable*/
  16.         qcom,pageen1 = <0 0x0 0 0x0 0 0>;
  17.         qcom,poll1 = <0 0x0 0 0x0 0 0>;
  18.         qcom,mem1 = <0 0x5001 2 0 1 1>;

  19.         qcom,page2 = <1 0x3d84 2 0xc0 1 1>;/*往0x3d84写入0xc0:Enable partial OTP write */
  20.         qcom,pageen2 = <0 0x0 2 0x0 0 0>;
  21.         qcom,poll2 = <0 0x0 2 0x0 0 0>;
  22.         qcom,mem2 = <0 0x0 2 0 0 0>;

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

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

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

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

  39.         qcom,page7 = <1 0x3d81 2 0x01 1 10>;/*往0x3d81写入0x01:把OTP数据加载到buffer中 */
  40.         qcom,pageen7 = <0 0x0 0 0x0 0 0>;
  41.         qcom,poll7 = <0 0x0 0 0x0 0 0>;
  42.         qcom,mem7 = <256 0x7010 2 0 1 1>;/*从0x7010开始读取256个数据*/

  43.         qcom,page8 = <1 0x5001 2 0x0a 1 1>;/*往0x5001写0x0a:OTP disable*/
  44.         qcom,pageen8 = <0 0x0 0 0x0 0 0>;
  45.         qcom,poll8 = <0 0x0 0 0x0 0 0>;
  46.         qcom,mem8 = <0 0x0 2 0 1 1>;

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

  51.         cam_vio-supply = <&pm8916_l10>;/*供电相关:和camera一致即可*/
  52.         qcom,cam-vreg-name = "cam_vio";;/*硬件上只需IO供电,其他AVDD和DVDD都会被IO拉起来*/
  53.         qcom,cam-vreg-type = <0>;
  54.         qcom,cam-vreg-min-voltage = <1800000>;
  55.         qcom,cam-vreg-max-voltage = <2800000>;
  56.         qcom,cam-vreg-op-mode = <80000>;
  57.         pinctrl-names = "cam_default", "cam_suspend";
  58.         pinctrl-0 = <&cam_sensor_mclk1_default &cam_sensor_front_default>;
  59.         pinctrl-1 = <&cam_sensor_mclk1_sleep &cam_sensor_front_sleep>;
  60.         gpios = <&msm_gpio 26 0>,/*GPIO相关:和camera一致即可*/
  61.         <&msm_gpio 28 0>,
  62.         <&msm_gpio 33 0>;
  63.         qcom,gpio-reset = <1>;
  64.         qcom,gpio-standby = <2>;
  65.         qcom,gpio-req-tbl-num = <0 1 2>;
  66.         qcom,gpio-req-tbl-flags = <1 0 0>;
  67.         qcom,gpio-req-tbl-label = "CAMIF_MCLK",
  68.         "CAM_RESET1",
  69.         "CAM_STANDBY";
  70.         qcom,cam-power-seq-type =/*eeprom的上电时序:和camera sensor的一致*/
  71.         "sensor_vreg","sensor_gpio", "sensor_gpio","sensor_clk";
  72.         qcom,cam-power-seq-val =
  73.         "cam_vio",
  74.         "sensor_gpio_standby",
  75.         "sensor_gpio_reset",
  76.         "sensor_cam_mclk";
  77.         qcom,cam-power-seq-cfg-val = <1 1 1 24000000>;
  78.         qcom,cam-power-seq-delay = <10 10 10 5>;

  79.         clocks = <&clock_gcc clk_mclk0_clk_src>,/*clock:和camera一致即可*/
  80.         <&clock_gcc clk_gcc_camss_mclk0_clk>;
  81.         clock-names = "cam_src_clk", "cam_clk";
  82.     };
复制代码
  1. qcom,camera@1 {//在camera中应用eeprom1
  2. ···
  3.         qcom,eeprom-src = <&eeprom1>;
  4. ···
  5. }
复制代码



属性节点含义
  • 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
  1. vendor/qcom/proprietary/mm-camera/mm-camera2/media-controller/
  2. modules/sensors/sensor_libs/ov5675_back/ov5675_back_lib.c
  3. static sensor_lib_t sensor_lib_ptr = {
  4.   /* sensor eeprom name */
  5.   .eeprom_name = "ov5675_back",
  6. }
复制代码

  • qcom,slave-addr = <0x20>;
    I2C 设备地址
  • cam_vio-supply = <&pm8916_l10>;
    供电电源
  • qcom,cam-power-seq-type
    上电时序


  1. 上电的类型
  2. qcom,cam-power-seq-type = "sensor_vreg","sensor_gpio", "sensor_gpio","sensor_clk";
  3. 上电类型的对应的val
  4. qcom,cam-power-seq-val = "cam_vio","sensor_gpio_standby","sensor_gpio_reset","sensor_cam_mclk";
  5. 上电时序的值:除了clock配置成相应的值,其他全配置1
  6. qcom,cam-power-seq-cfg-val = <1 1 1 24000000>;
  7. 上电延迟时间
  8. 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
    读写规则


  1. qcom,page7 = <1 0x3d81 2 0x01 1 10>;/*往0x3d81写入0x01:把OTP数据加载到buffer中 */
  2. qcom,pageen7 = <0 0x0 0 0x0 0 0>;
  3. qcom,poll7 = <0 0x0 0 0x0 0 0>;
  4. qcom,mem7 = <256 0x7010 2 0 1 1>;/*从0x7010开始读取256个数据*/
复制代码




到此,dtsi的配置就完成了!!!
3.3 软件驱动配置
1.添加新的EEPROM驱动
  1. vendor/qcom/proprietary/mm-camera/mm-camera2/media-controller/modules/
  2. sensors/eeprom_libs/ov5675_back
  3. - ov5675_back.c
  4. - Android.mk
复制代码



任何新 <eeprom>.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的配置
  1. eeprom0: qcom,eeprom@a0 {
  2.         cell-index = <0>;
  3.         reg = <0xa0>;
  4.         qcom,eeprom-name = "gc8034_otp";
  5.         compatible = "qcom,eeprom";
  6.         qcom,slave-addr = <0xa0>;
  7.         qcom,cci-master = <0>;
  8.         qcom,num-blocks = <1>;
  9.         qcom,page0 = <0 0 0 0 0 0>;
  10.         qcom,pageen0 = <0 0x0 0 0x0 0 0>;
  11.         qcom,poll0 = <0 0x0 0 0x0 0 0>;
  12.         qcom,mem0 = <1813 0x0000 2 0 1 1>;
  13.         cam_vio-supply = <&pm8916_l10>;
  14.         qcom,cam-vreg-name = "cam_vio";
  15.         qcom,cam-vreg-type = <0>;
  16.         qcom,cam-vreg-min-voltage = <1800000>;
  17.         qcom,cam-vreg-max-voltage = <2800000>;
  18.         qcom,cam-vreg-op-mode = <80000>;
  19.         qcom,cam-power-seq-type = "sensor_vreg";
  20.         qcom,cam-power-seq-val ="cam_vio";
  21.         qcom,cam-power-seq-cfg-val = <1>;
  22.         qcom,cam-power-seq-delay = <10>;
  23.     };
复制代码

最关键的地方就是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
  1. static int i2c_check_client_addr_validity(const struct i2c_client *client)
  2. {
  3.    
  4.     if (client->flags & I2C_CLIENT_TEN) {
  5.         /* 10-bit address, all values are valid */
  6.         if (client->addr > 0x3ff)
  7.             return -EINVAL;
  8.     } else {
  9.         if (client->addr == 0xa0)//让a0地址合法化!!!
  10.             return 0;
  11.         /* 7-bit address, reject the general call address */
  12.         if (client->addr == 0x00 || client->addr > 0x7f)
  13.             return -EINVAL;
  14.     }   
  15.     return 0;
  16. }
复制代码



供电这一块,eeprom只需要IO供电即可:因此配置就更简单了
  1. cam_vio-supply = <&pm8916_l10>;
  2.         qcom,cam-vreg-name = "cam_vio";
  3.         qcom,cam-vreg-type = <0>;
  4.         qcom,cam-vreg-min-voltage = <1800000>;
  5.         qcom,cam-vreg-max-voltage = <2800000>;
  6.         qcom,cam-vreg-op-mode = <80000>;
  7.         qcom,cam-power-seq-type = "sensor_vreg";
  8.         qcom,cam-power-seq-val ="cam_vio";
  9.         qcom,cam-power-seq-cfg-val = <1>;
  10.         qcom,cam-power-seq-delay = <10>;
复制代码
读写规则:直接从0x00开始读1813个数据,不需要操作任何寄存器!
  1. qcom,mem0 = <1813 0x0000 2 0 1 1>;
复制代码


例子三:以GC8034为例子(数据烧录在Camera Sensor中)
GC8034的读写规则比较复杂,和高通要求的不一样!





高通的源码是给一个初始地址,然后不停+1的往后读取数据,最后保存在buffer中!
GC8034是去读d7这个寄存器的值!(这些读写规则,要多和模组厂跟sensor厂沟通)
因此要改动kernel层的源码
  1. static int read_eeprom_memory(struct msm_eeprom_ctrl_t *e_ctrl,
  2.     struct msm_eeprom_memory_block_t *block)
  3.         if (emap[j].mem.valid_size) {
  4.             /*   galaxycore start  */
  5.             if(0 == strcmp(eb_info->eeprom_name,"gc8034_otp")){
  6.                     e_ctrl->i2c_client.addr_type = 1;  /* luyi */
  7.                     /*读取0xf4到gc_readf4变量中*/
  8.                     rc=e_ctrl->i2c_client.i2c_func_tbl->i2c_read(
  9.                             &(e_ctrl->i2c_client), 0xf4, &gc_readf4, emap[j].mem.data_t);
  10.                     /*往d4寄存器写page和高8位地址*/
  11.                     e_ctrl->i2c_client.i2c_func_tbl->i2c_write(
  12.                             &(e_ctrl->i2c_client), 0xd4, (emap[j].mem.addr >> 8) & 0xff, emap[j].mem.data_t);
  13.                     /*往d5寄存器写低8位地址*/
  14.                     e_ctrl->i2c_client.i2c_func_tbl->i2c_write(
  15.                             &(e_ctrl->i2c_client), 0xd5, emap[j].mem.addr & 0xff, emap[j].mem.data_t);
  16.                     /*往f3寄存器写入0x20:OTP read 模式*/
  17.                     e_ctrl->i2c_client.i2c_func_tbl->i2c_write(
  18.                             &(e_ctrl->i2c_client), 0xf3, 0x20, emap[j].mem.data_t);
  19.                     /*往f4寄存器的第2位置1,表示地址自动++(按照1 个byte=8bit的方式)*/
  20.                     e_ctrl->i2c_client.i2c_func_tbl->i2c_write(
  21.                             &(e_ctrl->i2c_client), 0xf4, gc_readf4 | 0x02, emap[j].mem.data_t);
  22.                     /*往f3寄存器写入80,设置自动读取信号*/
  23.                     e_ctrl->i2c_client.i2c_func_tbl->i2c_write(
  24.                             &(e_ctrl->i2c_client), 0xf3, 0x80, emap[j].mem.data_t);
  25.                     msleep(emap[j].mem.delay);//延时
  26.                     for(gc = 0; gc < emap[j].mem.valid_size; gc++){
  27.                         msleep(emap[j].mem.delay);
  28.                         rc=e_ctrl->i2c_client.i2c_func_tbl->i2c_read(//读d7寄存器的值到gc_read变量中
  29.                                 &(e_ctrl->i2c_client), 0xd7, &gc_read, emap[j].mem.data_t);
  30.                         if (rc < 0) {
  31.                             pr_err("%s: read failed %d \n", __func__, __LINE__);
  32.                             return rc;
  33.                         }
  34.                         *memptr = (uint8_t)gc_read;//把读出来的值保持到memptr 中
  35.                         memptr++;
  36.                     }
  37.                     e_ctrl->i2c_client.i2c_func_tbl->i2c_write(//读完复位成初始状态
  38.                             &(e_ctrl->i2c_client), 0xf3, 0x00, emap[j].mem.data_t);
  39.                     e_ctrl->i2c_client.i2c_func_tbl->i2c_write(//读完复位成初始状态
  40.                             &(e_ctrl->i2c_client), 0xf4, gc_readf4 & 0xfd, emap[j].mem.data_t);
  41.                 }
  42.                 /*galaxycore end*/
  43.             else{//高通平台默认的读取方式
  44.                 e_ctrl->i2c_client.addr_type = emap[j].mem.addr_t;
  45.             rc = e_ctrl->i2c_client.i2c_func_tbl->i2c_read_seq(
  46.                 &(e_ctrl->i2c_client), emap[j].mem.addr,
  47.                 memptr, emap[j].mem.valid_size);
  48.                 pr_err("%s:travis read addr = %d,value = %d\n\n", __func__,emap[j].mem.addr,memptr[0]);
  49.             if (rc < 0) {
  50.                 pr_err("%s: read failed\n", __func__);
  51.                 return rc;
  52.             }
  53.             memptr += emap[j].mem.valid_size;
  54.             }
  55.         }
  56. }
复制代码


这样子会带来问题,导致kernel启动时间加长了30s,从而整个系统启动时间就延迟了30s!!!

Stay Hungry,Stay Foolish!




回复

使用道具 举报

34

主题

40

帖子

240

积分

超级版主

Rank: 8Rank: 8

积分
240
发表于 2020-7-29 15:04:04 | 显示全部楼层
你就是csdn的“c枫_撸码的日子”
回复

使用道具 举报

16

主题

22

帖子

167

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
167
发表于 2020-7-29 22:54:34 | 显示全部楼层
maomaochong 发表于 2020-7-29 15:04
你就是csdn的“c枫_撸码的日子”

厉害厉害
回复

使用道具 举报

1

主题

9

帖子

27

积分

新手上路

Rank: 1

积分
27
发表于 2020-7-29 23:21:52 | 显示全部楼层
都是csdn的大神啊。
回复

使用道具 举报

7

主题

11

帖子

96

积分

版主

Rank: 7Rank: 7Rank: 7

积分
96
 楼主| 发表于 2020-7-31 08:17:15 | 显示全部楼层
maomaochong 发表于 2020-7-29 15:04
你就是csdn的“c枫_撸码的日子”

不是什么大神 菜鸡一个
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

Archiver|手机版|小黑屋|深圳市科维通信技术有限公司论坛 ( 粤ICP备20056433号 )

GMT+8, 2021-9-24 04:37 , Processed in 0.045671 second(s), 18 queries .

Powered by Discuz! X3.4

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表