一、前言
关于高通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数据
···
}
到此,我们整个流程就都打通了!