[Camera Topic] Qcom-Qualcomm OTP Programming and Debugging Guide-Part 1

1. Foreword

There is very little knowledge about Qualcomm OTP programming on the Internet, and the official documents are not so clear, so here is a dry article!
The complete guide to OTP programming is divided into two parts, the first and the second.
Top: Mainly talk about OTP knowledge and debugging process.
Next: Mainly talk about the source code of OTP.

Knowledge points in this article:

  • 1. The basic concept of OTP
  • 2. The function of OTP
  • 3. OTP debugging process

2. Knowledge points

1. The basic concept of OTP (what is it)

OTP (One Time Programmable) means one-time programmable. After the program or data is burned into the [memory], it cannot be changed or cleared again.

Data type of OTP programming
Generally include:

  • AF: Auto Focus Calibration Data
  • AWB: white balance calibration data
  • LSC: Lens Shading Calibration
  • Moudle Info: Module information, including the production date and month of the module, module ID, etc.


Take AF as an example:

AF data burned by vendor factory:

Page: 3, Addr: 0x01D0, Data: 0x04
Page: 3, Addr: 0x01D8, Data: 0x01
Page: 3, Addr: 0x01E0, Data: 0xBE
Page: 3, Addr: 0x01E8, Data: 0x54
Page: 3, Addr: 0x01F0, Data: 0x15
Page: 3, Addr: 0x01F8, Data: 0x00

Types of OTP memory
According to debugging experience, there are currently two mainstream types:

  • 1. The OTP data is burned in the register of the sensor.
    This solution saves money and does not require additional storage devices, but the storage space is small, and it is not suitable if the amount of data to be burned is too large.

  • OTP data is burned in EEPROM:
    EEPROM (Electrically Erasable Programmable read only memory) refers to electrically erasable programmable read-only memory,
    It is a memory chip that does not lose data after power failure.
    The advantage of this solution is that the storage space is large. If there is too much data, this solution is needed. The disadvantage is that there is an additional independent EEPROM storage device.
    Spend some money (5 cents or so).

2. The role of OTP (why)

OTP is used for calibration of camera sensor.
Because there will be great differences in the production of modules, in order to ensure the consistency of the effect,
The module factory will select some modules as golden, and then calibrate the corresponding parameters of other modules to be the same as these golden,
(golden is not the best module, nor the worst module, but the most average module in all aspects).

3. OTP debugging process (how to do it)

Debugging platform: 8909 (relatively low-end)

PS: The OTP in the Qualcomm source code refers to the EEPROM driver.

Example 1: Take OV5675 as an example (data is burned in Camera Sensor)

3.1 Preparations for OTP commissioning

  • Get relevant information from the datasheet
    OTP Datasheet(OV5675 Calibration and OTP Programming Guid)
    Camera sensor Datasheet(sensor_OV05675-GA4A.pdf)
    a. Find out the power-on sequence (this is consistent with the camera power-on sequence)

b. Get slave address

On hardware, our pin is pulled high, so I2C addr = 0x20

c. Figure out the rules of reading and writing

d. Other

1. Power supply: cam_vio-supply = < & amp;pm8916_l10>;
2. clock:
   clocks = < & amp; clock_gcc clk_mclk0_clk_src>, < & amp; 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 Configure DTSI file

EEPROM data is read at device startup. The memory map needs to be converted to the corresponding attribute node in dtsi.
It must specify the regulator (power supply), clock signal, power supply startup sequence, device address, and read sequence.
Path: kernel/arch/arm/boot/dts/qcom/msm8909-pm8916-camera-sensor-i18.dtsi

eeprom1: qcom,eeprom@20 {
        cell-index = <1>;/*Assigned to eeprom subdev, only one can*/
        reg = <0x20>;/*registration address*/
        qcom,eeprom-name = "ov5675_back";/*eeprom driver name, must be consistent with the driver name*/
        compatible = "qcom,eeprom";/*Matching nodes are all this value*/
        qcom,slave-addr = <0x20>;/*i2c address*/
        qcom,cci-master = <0>;/*The default is 0*/
        qcom,num-blocks = <10>;/*The number of pages configured below*/

        /* read and write rules */
        qcom,page0 = <1 0x0100 2 0x01 1 10>;/*steam on this operation is not necessary*/
        qcom,pageen0 = <0 0x0 0 0x0 0 0>;
        qcom,poll0 = <0 0x0 0 0x0 0 0>;
        qcom,mem0 = <0 0x0 2 0 1 1>;
        /*Initialization operation*/
        qcom,page1 = <1 0x5001 2 0x02 1 1>;/*write 0x02 to 0x5001: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>;/*write 0xc0 to 0x3d84: 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>;/*write 0x70 to 0x3d88: start address high 8-bit address*/
        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>;/*write 0x10 to 0x3d88: start address lower 8-bit address*/
        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>;/*write 0x72 to 0x3d8a: end address high 8-bit address*/
        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>;/*write 0x29 to 0x3d8b: end address lower 8-bit address*/
        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>;/*write 0x01 to 0x3d81: load OTP data into 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>;/*read 256 data from 0x7010*/

        qcom,page8 = <1 0x5001 2 0x0a 1 1>;/*write 0x0a to 0x5001: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 = < & amp;pm8916_l10>; /* power supply related: just consistent with camera*/
        qcom,cam-vreg-name = "cam_vio";;/*The hardware only needs IO power supply, other AVDD and DVDD will be pulled up by 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 = < & amp;cam_sensor_mclk1_sleep & amp;cam_sensor_front_sleep>;
        gpios = < & amp;msm_gpio 26 0>, /* GPIO related: consistent with 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 =/*power-on sequence of eeprom: consistent with 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 = < & amp;clock_gcc clk_mclk0_clk_src>, /*clock: just consistent with camera*/
        < & clock_gcc clk_gcc_camss_mclk0_clk>;
        clock-names = "cam_src_clk", "cam_clk";
    };
qcom, camera@1 {//apply eeprom1 in camera
···
        qcom,eeprom-src = < & eeprom1>;
···
}

Attribute node meaning

  • cell-index = <1>;
    This node is used for eeprom subdev to register subdev_id, only one is enough!
  • reg = <0x20>
    Registered address: High-end platforms require this address to be unique, and low-end platforms use this address to communicate with i2c.
    To be on the safe side, set it to the i2c address uniformly.
  • qcom,eeprom-name = “ov5675_back”;
    This name must be consistent with the name of the eeprom driver, for example
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 device address
* cam_vio-supply = < & pm8916_l10>;
  Power supply
* qcom,cam-power-seq-type
power on sequence
```c
Type of power-up
qcom,cam-power-seq-type = "sensor_vreg","sensor_gpio", "sensor_gpio","sensor_clk";
The corresponding val of the power-on type
qcom,cam-power-seq-val = "cam_vio","sensor_gpio_standby","sensor_gpio_reset","sensor_cam_mclk";
The value of the power-on sequence: except the clock is configured to the corresponding value, other full configurations 1
qcom,cam-power-seq-cfg-val = <1 1 1 24000000>;
Power-on delay time
qcom,cam-power-seq-delay = <10 10 10 5>;

In fact, this power-on sequence is consistent with the power-on sequence of Camera Sensor! for example

  • qcom,page0=
    =
    Address type: 1 means 1 byte, 2 means 2 byte = 1 word
    Data type: 1 means 1 byte, 2 means 2 byte = 1 word
    reading and writing rules
qcom,page7 = <1 0x3d81 2 0x01 1 10>;/*write 0x01 to 0x3d81: load OTP data into 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>;/*read 256 data from 0x7010*/


At this point, the configuration of dtsi is complete! ! !

3.3 Software driver configuration

1. Add new EEPROM driver

vendor/qcom/proprietary/mm-camera/mm-camera2/media-controller/modules/
sensors/eeprom_libs/ov5675_back
* ov5675_back.c
* Android.mk

Any new .c file should map and define the following function pointers.
All functions not defined in this EEPROM driver must be set to NULL.


.get_calibration_items() – This function should return the configurations supported by the EEPROM module.
Sets the specified flag to TRUE or FALSE based on the configuration supported by the EEPROM.

  • Is_insensor – Set this flag to TRUE if the sensor module itself supports EEPROM configuration.
    No external EEPROM is available.
  • Is_afc – Set this flag to TRUE if AF calibration is supported.
  • Is_wbc – Set this flag to TRUE if white balance calibration is supported.
  • Is_lsc – Set this flag to TRUE if lens shading calibration is supported.
  • Is_dpc – Set this flag to TRUE if defective pixel correction is supported

.format_calibration_data() – This function is used to format data that can be written to the eeprom/sensor module

OTP Data Application

  • .do_af_calibration() – This function handles all AF related calibration operations,
    Such as formatting data and writing it to EEPROM to perform AF calibration.
  • .do_wbc_calibration() – This function handles all white balance related calibration operations,
    Such as formatting data and writing it to EEPROM to perform white balance calibration.
  • .do_lsc_calibration() – This function handles all calibration operations related to lens shading correction,
    Such as formatting data and writing it to EEPROM to perform lens shading calibration.
  • .do_dpc_calibration() – This function handles all calibration operations related to defective pixel correction,
    Such as formatting data and writing it to EEPROM to perform defective pixel correction

Example 2: Take the independent EEPROM as an example (data is burned in the independent EEPROM)

The steps are the same as Example 1, the key lies in the configuration of 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>;
    };

The most critical place is reg = <0xa0>;
Here it should be configured as an I2C address. When reading the array, I2C will automatically communicate with a0>>1=0x50!
Of course, high-end platforms do not need to pay attention to reg, just configure the unique one, the best way is to configure it as an i2c address!

The 8909 platform does not support the configuration of reg as a0, and the effective address in the kernel is 0x00~0x7f directly. If it is configured as a0,
An error will be reported: Invalid7-bit I2C address 0xa0!!!
So you need to modify the kernel:
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)//Legalize the a0 address! ! !
            return 0;
        /* 7-bit address, reject the general call address */
        if (client->addr == 0x00 || client->addr > 0x7f)
            return -EINVAL;
    }
    return 0;
}

For power supply, eeprom only needs IO power supply: so the configuration is simpler

 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>;

Read and write rules: read 1813 data directly from 0x00, no need to operate any registers!

qcom,mem0 = <1813 0x0000 2 0 1 1>;

Example 3: Take GC8034 as an example (data is burned in Camera Sensor)

The reading and writing rules of GC8034 are more complicated, which are different from those required by Qualcomm!

Qualcomm’s source code is to give an initial address, then read the data continuously + 1, and finally save it in the buffer!
GC8034 is to read the value of the register d7! (These reading and writing rules should be communicated with the module factory and the sensor factory)
Therefore, to change the source code of the kernel layer

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 */
                    /* read 0xf4 to gc_readf4 variable */
                    rc=e_ctrl->i2c_client.i2c_func_tbl->i2c_read(
                             & amp;(e_ctrl->i2c_client), 0xf4, & amp;gc_readf4, emap[j].mem.data_t);
                    /* Write page and high 8-bit address to d4 register */
                    e_ctrl->i2c_client.i2c_func_tbl->i2c_write(
                             & amp;(e_ctrl->i2c_client), 0xd4, (emap[j].mem.addr >> 8) & amp; 0xff, emap[j].mem.data_t);
                    /* Write the lower 8-bit address to the d5 register */
                    e_ctrl->i2c_client.i2c_func_tbl->i2c_write(
                             & amp;(e_ctrl->i2c_client), 0xd5, emap[j].mem.addr & amp; 0xff, emap[j].mem.data_t);
                    /* Write 0x20: OTP read mode to f3 register */
                    e_ctrl->i2c_client.i2c_func_tbl->i2c_write(
                             & amp;(e_ctrl->i2c_client), 0xf3, 0x20, emap[j].mem.data_t);
                    /*The second position of the f4 register is set to 1, indicating that the address is automatically + + (according to the method of 1 byte=8bit)*/
                    e_ctrl->i2c_client.i2c_func_tbl->i2c_write(
                             & amp;(e_ctrl->i2c_client), 0xf4, gc_readf4 | 0x02, emap[j].mem.data_t);
                    /* Write 80 to the f3 register, set the automatic read signal */
                    e_ctrl->i2c_client.i2c_func_tbl->i2c_write(
                             & amp;(e_ctrl->i2c_client), 0xf3, 0x80, emap[j].mem.data_t);
                    msleep(emap[j].mem.delay);//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(//Read the value of the d7 register to the gc_read variable
                                 & amp;(e_ctrl->i2c_client), 0xd7, & amp;gc_read, emap[j].mem.data_t);
                        if (rc < 0) {
                            pr_err("%s: read failed %d \
", __func__, __LINE__);
                            return rc;
                        }
                        *memptr = (uint8_t)gc_read;//keep the read value in memptr
                        memptr++;
                    }
                    e_ctrl->i2c_client.i2c_func_tbl->i2c_write(//Reset to initial state after reading
                             & amp;(e_ctrl->i2c_client), 0xf3, 0x00, emap[j].mem.data_t);
                    e_ctrl->i2c_client.i2c_func_tbl->i2c_write(//Reset to initial state after reading
                             & amp;(e_ctrl->i2c_client), 0xf4, gc_readf4 & amp; 0xfd, emap[j].mem.data_t);
                }
                /*galaxycore end*/
            else{//Qualcomm platform default reading method
                e_ctrl->i2c_client.addr_type = emap[j].mem.addr_t;
            rc = e_ctrl->i2c_client.i2c_func_tbl->i2c_read_seq(
                 & amp;(e_ctrl->i2c_client), emap[j].mem.addr,
                memptr, emap[j].mem.valid_size);
                pr_err("%s:travis read addr = %d,value = %d\
\
", __func__,emap[j].mem.addr,memptr[0]);
            if (rc < 0) {
                pr_err("%s: read failed\
", __func__);
                return rc;
            }
            memptr + = emap[j].mem.valid_size;
            }
        }
}

Stay Hungry, Stay Foolish!

———————
Author: c Feng_The day of the code
Source: CSDN
Original: https://blog.csdn.net/justXiaoSha/article/details/88071908
Copyright statement: This article is the author’s original article, please attach the blog post link for reprinting!
Content analysis By: CSDN, CNBLOG blog post one-click reprint plug-in