【Camera专题】Qcom-高通OTP编程调试指南-下

一、前言

关于高通OTP编程的知识,网上少得可怜,官方文档又没有那么清晰,于是就来一篇干货吧!
OTP编程完全指南分上、下2篇。
上:主要讲OTP的知识和调试流程。
下:主要讲OTP的源码。
Qcom-高通OTP编程调试指南-上
Qcom-高通OTP编程调试指南-下

二、源码分析

1.kernel层

1.1 eeprom的初始化
kernel/drivers/media/platform/msm/camera_v2/sensor/eeprom/msm_eeprom.c

static int __init msm_eeprom_init_module(void)
{
    
    
    int rc = 0; 
    CDBG("%s E\n", __func__);
    rc = platform_driver_register(&msm_eeprom_platform_driver);
    CDBG("%s:%d platform rc %d\n", __func__, __LINE__, rc); 
    rc = spi_register_driver(&msm_eeprom_spi_driver);
    CDBG("%s:%d spi rc %d\n", __func__, __LINE__, rc); 
    return i2c_add_driver(&msm_eeprom_i2c_driver);
}

函数主要是实现了两个功能:

  • 通过platform_driver_register函数注册平台驱动(msm_eeprom_platform_driver)
  • 将msm_eeprom_i2c_driver挂载i2c总线上

1.2 匹配驱动和设备
这之后,就会根据名称匹配驱动driver和设备device

eeprom0: qcom,eeprom@6e {
    
    
        cell-index = <0>;
        reg = <0x6e>;
        qcom,eeprom-name = "gc8034_otp";
        compatible = "qcom,eeprom";
        qcom,slave-addr = <0x6e>;
static struct i2c_driver msm_eeprom_i2c_driver = {
    
    

    .id_table = msm_eeprom_i2c_id,
    .probe  = msm_eeprom_i2c_probe,
    .remove = __exit_p(msm_eeprom_i2c_remove),
    .driver = {
    
    
        .name = "qcom,eeprom",
        .owner = THIS_MODULE,
        .of_match_table = msm_eeprom_i2c_dt_match,
    },


};

**compatible = “qcom,eeprom”**和 .driver = {.name = “qcom,eeprom”,匹配上了,
系统就去调用probe函数
probe = msm_eeprom_i2c_probe

1.3 匹配成功,调用probe函数

static int msm_eeprom_i2c_probe(struct i2c_client *client,
             const struct i2c_device_id *id)
{
    
    
···
    e_ctrl->userspace_probe = 0;//是否在用户空间执行probe函数
    e_ctrl->is_supported = 0;

    /* 设置设备类型为I2C设备 */
    e_ctrl->eeprom_device_type = MSM_CAMERA_I2C_DEVICE;

    e_ctrl->i2c_client.i2c_func_tbl = &msm_eeprom_qup_func_tbl;

···
    /*读取dtsi配置的cell_id*/
    rc = of_property_read_u32(of_node, "cell-index", &cell_id);
    CDBG("cell-index %d, rc %d\n", cell_id, rc);
···
    e_ctrl->subdev_id = cell_id;
    /*读取dtsi配置的"qcom,eeprom-name"节点,这里为gc8034_otp*/
    rc = of_property_read_string(of_node, "qcom,eeprom-name",
        &eb_info->eeprom_name);
    CDBG("%s qcom,eeprom-name %s, rc %d\n", __func__,
        eb_info->eeprom_name, rc);
···
     /*读取dtsi配置的上电时序配置*/
    rc = msm_eeprom_get_dt_data(e_ctrl);
        /*读取dtsi配置的i2c-freq-mode节点*/
        rc = of_property_read_u32(of_node, "qcom,i2c-freq-mode",
            &e_ctrl->i2c_freq_mode);
 ···


      // msm_eeprom_parse_memory_map用于解析以下节点
       //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>;
    */
        rc = msm_eeprom_parse_memory_map(of_node, &e_ctrl->cal_data);
        if (rc < 0)
            goto board_free;
        /*开始上电操作*/
        rc = msm_camera_power_up(power_info, e_ctrl->eeprom_device_type,
            &e_ctrl->i2c_client);
···
       /*读取内存的数据*/
        rc = read_eeprom_memory(e_ctrl, &e_ctrl->cal_data);
        if (rc < 0) {
    
    
            pr_err("%s read_eeprom_memory failed\n", __func__);
            goto power_down;
        }
        for (j = 0; j < e_ctrl->cal_data.num_data; j++)
            CDBG("memory_data[%d] = 0x%X\n", j,
                e_ctrl->cal_data.mapdata[j]);

        e_ctrl->is_supported |= msm_eeprom_match_crc(&e_ctrl->cal_data);
        /*开始下电操作*/
        rc = msm_camera_power_down(power_info,
            e_ctrl->eeprom_device_type, &e_ctrl->i2c_client);
        if (rc) {
    
    
            pr_err("failed rc %d\n", rc);
            goto memdata_free;
        }
    } else
        e_ctrl->is_supported = 1;
    //初始化从设备以及绑定函数操作集
    v4l2_subdev_init(&e_ctrl->msm_sd.sd,e_ctrl->eeprom_v4l2_subdev_ops);
    v4l2_set_subdevdata(&e_ctrl->msm_sd.sd, e_ctrl);
    e_ctrl->msm_sd.sd.internal_ops = &msm_eeprom_internal_ops;
    e_ctrl->msm_sd.sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
    snprintf(e_ctrl->msm_sd.sd.name,ARRAY_SIZE(e_ctrl->msm_sd.sd.name), "msm_eeprom");
    media_entity_init(&e_ctrl->msm_sd.sd.entity, 0, NULL, 0);
    e_ctrl->msm_sd.sd.entity.type = MEDIA_ENT_T_V4L2_SUBDEV;
    e_ctrl->msm_sd.sd.entity.group_id = MSM_CAMERA_SUBDEV_EEPROM;
    msm_sd_register(&e_ctrl->msm_sd);

#ifdef CONFIG_COMPAT
    msm_cam_copy_v4l2_subdev_fops(&msm_eeprom_v4l2_subdev_fops);
    msm_eeprom_v4l2_subdev_fops.compat_ioctl32 =
        msm_eeprom_subdev_fops_ioctl32;
    e_ctrl->msm_sd.sd.devnode->fops = &msm_eeprom_v4l2_subdev_fops;
#endif
···
}

这个函数主要工作:

  • msm_eeprom_get_dt_data(e_ctrl);读取dtsi配置的上电时序配置/
  • msm_eeprom_parse_memory_map用于解析以下节点
       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>;
  • msm_camera_power_up上电
  • read_eeprom_memory读取OTP数据
  • msm_camera_power_down下电

说白了,前面无非就是去读取dtsi的配置,进行上电,然后读取OTP数据,下电,
这就是整个probe函数的流程!


1.4 读取OTP数据:read_eeprom_memory

static int read_eeprom_memory(struct msm_eeprom_ctrl_t *e_ctrl,
    struct msm_eeprom_memory_block_t *block)
{
    
    
···
    eb_info = e_ctrl->eboard_info;
//这里block->num_map是多少,就循环多少次
    for (j = 0; j < block->num_map; j++) {
    
    
        if (emap[j].saddr.addr) {
    
    
            eb_info->i2c_slaveaddr = emap[j].saddr.addr;
            e_ctrl->i2c_client.cci_client->sid =
                    eb_info->i2c_slaveaddr >> 1;
            pr_err("qcom,slave-addr = 0x%X\n",
                eb_info->i2c_slaveaddr);
        }
//qcom,page7 = <1 0x3d81 2 0x01 1 10>; 
//这里emap[j].page.valid_size = 1,i2c_client.addr_type=2 emap[j].page.data_t=1
        if (emap[j].page.valid_size) {
    
    
            e_ctrl->i2c_client.addr_type = emap[j].page.addr_t;
           //往0x3d81写入0x01:把OTP数据加载到buffer中
            rc = e_ctrl->i2c_client.i2c_func_tbl->i2c_write(
                &(e_ctrl->i2c_client), emap[j].page.addr,
                emap[j].page.data, emap[j].page.data_t);
                msleep(emap[j].page.delay);
            if (rc < 0) {
    
    
                pr_err("%s: page write failed\n", __func__);
                return rc;
            }
        }
        if (emap[j].pageen.valid_size) {
    
    
            e_ctrl->i2c_client.addr_type = emap[j].pageen.addr_t;
            rc = e_ctrl->i2c_client.i2c_func_tbl->i2c_write(
                &(e_ctrl->i2c_client), emap[j].pageen.addr,
                emap[j].pageen.data, emap[j].pageen.data_t);
                msleep(emap[j].pageen.delay);
            if (rc < 0) {
    
    
                pr_err("%s: page enable failed\n", __func__);
                return rc;
            }
        }
        if (emap[j].poll.valid_size) {
    
    
            e_ctrl->i2c_client.addr_type = emap[j].poll.addr_t;
            rc = e_ctrl->i2c_client.i2c_func_tbl->i2c_poll(
                &(e_ctrl->i2c_client), emap[j].poll.addr,
                emap[j].poll.data, emap[j].poll.data_t,
                emap[j].poll.delay);
            if (rc < 0) {
    
    
                pr_err("%s: poll failed\n", __func__);
                return rc;
            }
        }

        if (emap[j].mem.valid_size) {
    
    
                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;
        }
        if (emap[j].pageen.valid_size) {
    
    
            e_ctrl->i2c_client.addr_type = emap[j].pageen.addr_t;
            rc = e_ctrl->i2c_client.i2c_func_tbl->i2c_write(
                &(e_ctrl->i2c_client), emap[j].pageen.addr,
                0, emap[j].pageen.data_t);
            if (rc < 0) {
    
    
                pr_err("%s: page disable failed\n", __func__);
                return rc;
            }
        }
    }
    return rc;
}

实际上,这个函数很简单
qcom,page0 =
= <有效值 地址 地址类型 数据 数据类型 延迟>

        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个数据*/

结合这个来看
首先是一个for循环 for(j = 0; j < block->num_map; j++)

这里循环的次数就是我们在kernel中配置的qcom,num-blocks = <10>
其次,分别对

  • emap[j].saddr.addr
  • emap[j].page
  • emap[j].pageen
  • emap[j].poll
  • emap[j].mem
    进行读或者写的操作
    我们以一个为例子进行分析,其他几个都是一样的
 qcom,mem7 = <256 0x7010 2 0 1 1>;/*从0x7010开始读取256个数据*/

上面是配置

 if (emap[j].mem.valid_size) {
    
    // j=7,emap[j].mem.valid_size=256
            //emap[j].mem.addr_t=2 代表2个byte
            e_ctrl->i2c_client.addr_type = emap[j].mem.addr_t;
            // emap[j].mem.addr=0710,emap[j].mem.valid_size=256
            // i2c_read_seq表示从0x7010开始自动+1读取256个数据,
            //数据保存在memptr指针中
            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指针+256
            memptr += emap[j].mem.valid_size;
        }

首先这里 j=7,emap[j].mem.valid_size=256
emap[j].mem.addr_t=2 代表2个byte
emap[j].mem.addr=0710,emap[j].mem.valid_size=256
i2c_read_seq表示从0x7010开始自动+1读取256个数据,
数据保存在memptr指针中
最后memptr指针+256

这就是把otp数据从寄存器中读出来,保存到memptr指针所指向的地址里!

这些数据可以在kernel层中看到:
在这里插入图片描述
值得注意的地方是:

这里的memory_data[0] = 0x10;//数组0保存的数据对应的地址是0x7010
这里的memory_data[1] = 0x4;//数组1保存的数据对应的地址是0x7011
·
·
·
这里的memory_data[10] = 0x4;//数组11保存的数据对应的地址是0x7021
以此类推。

到此 kernel的流程就完成了!

User层

上一节我们知道,数据最终通过函数保存到memptr指针所指向的地址里!
那么用户空间怎么调用的呢?

vendor/qcom/proprietary/mm-camera/mm-camera2/media-controller/modules/sensors/eeprom/eeprom.c

static int eeprom_get_info(void *ptr)
{
    
    
···

  cfg.cfgtype = CFG_EEPROM_GET_INFO;
  cfg.is_supported = 0;//默认不支持otp
//通过ioctl发送指令VIDIOC_MSM_EEPROM_CFG去调用kernel的函数获取数据,保存到cfg结构体中
  rc = ioctl(ep->fd, VIDIOC_MSM_EEPROM_CFG, &cfg);
  if (rc < 0) {
    
    
    SERR("VIDIOC_MSM_EEPROM_CFG(%d) failed!", ep->fd);
    return rc;
  }
 //把cfg结构体的值拿出来
  ep->eeprom_params.is_supported = cfg.is_supported;
//拷贝名称到ep->eeprom_params.eeprom_name
  memcpy(ep->eeprom_params.eeprom_name, cfg.cfg.eeprom_name,
     sizeof(ep->eeprom_params.eeprom_name));
  //如果支持otp
  if (cfg.is_supported) {
    
    
    SLOW("kernel returned eeprom supported name = %s\n", cfg.cfg.eeprom_name);
    cfg.cfgtype = CFG_EEPROM_GET_CAL_DATA;
    //发送指令VIDIOC_MSM_EEPROM_CFG到kernel去拿num_bytes这个数据
    rc = ioctl(ep->fd, VIDIOC_MSM_EEPROM_CFG, &cfg);
    if (rc < 0) {
    
     
      SERR("VIDIOC_MSM_EEPROM_CFG(%d) failed!", ep->fd);
      return rc;
    }    
    SLOW("kernel returned num_bytes =%d\n", cfg.cfg.get_data.num_bytes);
    //把支持的num_bytes给ep->eeprom_params.num_bytes
    ep->eeprom_params.num_bytes = cfg.cfg.get_data.num_bytes;
    if (ep->eeprom_params.num_bytes) {
    
    
     如果bytes大于0,就赋值给ep->eeprom_params.buffer
      ep->eeprom_params.buffer = (uint8_t *)malloc(ep->eeprom_params.num_bytes);
      if (!ep->eeprom_params.buffer){
    
    
        SERR("%s failed allocating memory\n",__func__);
        rc = -ENOMEM;
        return rc;
      }
    cfg.cfgtype = CFG_EEPROM_READ_CAL_DATA;//读取数据的指令
    cfg.cfg.read_data.num_bytes = ep->eeprom_params.num_bytes;
    cfg.cfg.read_data.dbuffer = ep->eeprom_params.buffer;
    //把数据读取到ep->eeprom_params.buffer中
    rc = ioctl(ep->fd, VIDIOC_MSM_EEPROM_CFG, &cfg);
    if (rc < 0) {
    
    
      SERR("CFG_EEPROM_READ_CAL_DATA(%d) failed!", ep->fd);
      return rc;
    }
    SLOW("kernel returned read buffer =%p\n", cfg.cfg.read_data.dbuffer);
    } else {
    
    
      /*Kernel return Zero bytes read*/
      SERR("kernel read num_bytes =%d\n", cfg.cfg.get_data.num_bytes);
      return -EINVAL;
    }
  }
  return rc;
}

这个函数已经添加了很多注释,理解起来也很简单。
我们拿出一个片段代码去具体分析,其他都是类似的。

 cfg.cfgtype = CFG_EEPROM_READ_CAL_DATA;//读取数据的指令
 cfg.cfg.read_data.num_bytes = ep->eeprom_params.num_bytes;//长度
 cfg.cfg.read_data.dbuffer = ep->eeprom_params.buffer;//
//把数据读取到ep->eeprom_params.buffer中
rc = ioctl(ep->fd, VIDIOC_MSM_EEPROM_CFG, &cfg);

这里通过ioctl发送VIDIOC_MSM_EEPROM_CFG指令到内核,
其中,cfgtype = CFG_EEPROM_READ_CAL_DATA;//读取数据的指令
我们跟踪到内核源码
kernel/drivers/media/platform/msm/camera_v2/sensor/eeprom/msm_eeprom.c

static long msm_eeprom_subdev_ioctl(struct v4l2_subdev *sd, 
        unsigned int cmd, void *arg)
{
    
    
    struct msm_eeprom_ctrl_t *e_ctrl = v4l2_get_subdevdata(sd);
    void __user *argp = (void __user *)arg;
    switch (cmd) {
    
    
    case VIDIOC_MSM_SENSOR_GET_SUBDEV_ID:
        return msm_eeprom_get_subdev_id(e_ctrl, argp);
    case VIDIOC_MSM_EEPROM_CFG:
        return msm_eeprom_config(e_ctrl, argp);//调用到这里
    default:
        return -ENOIOCTLCMD;
    }    

}

调用msm_eeprom_config(e_ctrl, argp);这个函数,继续看

static int msm_eeprom_config(struct msm_eeprom_ctrl_t *e_ctrl,
    void __user *argp)
{
    
    
···
    switch (cdata->cfgtype) {
    
    
    case CFG_EEPROM_READ_CAL_DATA:
        CDBG("%s E CFG_EEPROM_READ_CAL_DATA\n", __func__);
        rc = eeprom_config_read_cal_data(e_ctrl, cdata);
        break
    }
···
}

调用eeprom_config_read_cal_data(e_ctrl, cdata);这个函数,继续看

可以看到,最终调用的就是下面这个函数

static int eeprom_config_read_cal_data32(struct msm_eeprom_ctrl_t *e_ctrl,
    void __user *arg)
{
    
    
    int rc;
    uint8_t *ptr_dest = NULL;
    struct msm_eeprom_cfg_data32 *cdata32 =
        (struct msm_eeprom_cfg_data32 *) arg;
    struct msm_eeprom_cfg_data cdata;

    cdata.cfgtype = cdata32->cfgtype;
    cdata.is_supported = cdata32->is_supported;
    cdata.cfg.read_data.num_bytes = cdata32->cfg.read_data.num_bytes;
    /* check range */
    if (cdata.cfg.read_data.num_bytes >
        e_ctrl->cal_data.num_data) {
    
    
        CDBG("%s: Invalid size. exp %u, req %u\n", __func__,
            e_ctrl->cal_data.num_data,
            cdata.cfg.read_data.num_bytes);
        return -EINVAL;
    }
    if (!e_ctrl->cal_data.mapdata)
        return -EFAULT;

    ptr_dest = (uint8_t *) compat_ptr(cdata32->cfg.read_data.dbuffer);

    rc = copy_to_user(ptr_dest, e_ctrl->cal_data.mapdata,
        cdata.cfg.read_data.num_bytes);

    return rc;
}

这个函数很简单,首先检查一下cdata.cfg.read_data.num_bytes,这里是256
最后调用copy_to_user(ptr_dest, e_ctrl->cal_data.mapdata,cdata.cfg.read_data.num_bytes);
该函数的作用就是把内核空间的数据拷贝到用户空间
参数1:To 目标地址,这个地址是用户空间的地址;
参数2:From 源地址,这个地址是内核空间的地址;
参数3:N 将要拷贝的数据的字节数。

这些数据可以从log看到:
在这里插入图片描述

打印log的源码

vendor/qcom/proprietary/mm-camera/mm-camera2/media-controller/modules/sensors/eeprom/eeprom.c

static int32_t eeprom_set_bytestream(sensor_eeprom_data_t *e_ctrl, eeprom_params_t *e_params) {
    
    
···
 for (i = 0; i < e_ctrl->eeprom_params.num_bytes; i++) 
    SHIGH("e_ctrl->eeprom_params 0x%X", e_ctrl->eeprom_params.buffer[i]);//打印otp数据
···
}

到此,我们整个流程就都打通了!

Stay hungry,Stay foolish!

猜你喜欢

转载自blog.csdn.net/justXiaoSha/article/details/100174231