《【高通SDM660平台】(1) — Camera 驱动 Bringup Guide》
《【高通SDM660平台】(2) — Camera Kernel 驱动层代码逻辑分析》
《【高通SDM660平台】(3) — Camera V4L2 驱动层分析 》
《【高通SDM660平台】(4) — Camera Init 初始化流程 》
《【高通SDM660平台】(5) — Camera Open 流程》
《【高通SDM660平台】(6) — Camera getParameters 及 setParameters 流程》
《【高通SDM660平台】(7) — Camera onPreview 代码流程》
《【高通SDM660平台】(8) — Camera MetaData介绍》
《【高通SDM660平台 Android 10.0】(9) — Qcom Camera Daemon 代码分析》
《【高通SDM660平台 Android 10.0】(10) — Camera Sensor lib 与 Kernel Camera Probe 代码分析》
《【高通SDM660平台 Android 10.0】(11) — Eeprom lib 与 Kernel eeprom代码分析》
《【高通SDM660平台】Camera Capture 流程》
《【高通SDM660平台】Camera mm-qcamera-app 代码分析》
《【高通SDM660平台 Android 10.0】 — 高通马达、eeprom、flash 等外设代码分析》
以 imx258 为例,它使用的eeprom 是 rohm_brcg064gwz_3。
先说下,做OTP 的目的。
大家都知道,生产任何东西的时候,不可能做到一模一样,多多少少都会有些误差。
同样,Camera 模组也是一样,在同一批Camera 有品质好的,也有品质一般的,甚至也有少部份品质差的。
一般数量是呈正态分布,也就是,可能 10%是品质好,80%是品质一般的,10%是品质差的。
对于用户来说,他肯定要买品质好。
但对于Camera 模组厂来说,只卖品质好的肯定不行,因为大多数产品还是品质一般的,
所以就可以利用otp,将品质一般的Camera 模组 ,校准到和品质好的效果一样,就样就 OK 了。
这就是 OTP的由来,将大多数品质一般的模组,通过otp 数据校准,使其效果达到和 品质好的模组一样。
所以, 模组产商会对每一颗模组进行校准,也就是说,每个Camera 模组之间的 otp 数据都是不一样的,它是唯一的。
eeprom.c
是通用的eeprom
代码,它的相关代码,相信在前我们写的比较熟悉了
《【高通SDM660平台 Android 10.0】(9) — Qcom Camera Daemon 代码分析》
在该文件中,最重要的是 eeprom_process
函数。
它类似一个引导文件,通过它,最终还是会调用到 具体的camera eeprom 的代码中,因为每个eeprom 代码及数据处理方法都 是不一样的。
@ mm-camera/mm-camera2/media-controller/modules/sensors/eeprom/module/eeprom.c
static int32_t eeprom_process(void *eeprom_ctrl, sensor_submodule_event_type_t event, void *data)
{
sensor_eeprom_data_t *e_ctrl = (sensor_eeprom_data_t *)eeprom_ctrl;
switch(event) {
case EEPROM_READ_DATA:
rc = eeprom_get_info(e_ctrl);
break;
case EEPROM_SET_FORMAT_DATA:
rc = eeprom_format_calibration_data(e_ctrl);
eeprom_af_add_margin(e_ctrl);
break;
case EEPROM_CALIBRATE_WB:
eeprom_do_wb_calibration(e_ctrl, data);
break;
case EEPROM_CALIBRATE_WB_GREEN:
eeprom_do_wb_green_calibration(e_ctrl, data);
break;
case EEPROM_CALIBRATE_LSC:
eeprom_do_lsc_calibration(e_ctrl, data);
break;
case EEPROM_CALIBRATE_FOCUS_DATA:
e_ctrl->eeprom_afchroma = *((eeprom_set_chroma_af_t *)data);
eeprom_do_af_calibration(e_ctrl);
break;
case EEPROM_GET_ISINSENSOR_CALIB: {
int32_t *is_insensor = (int32_t *)data;
if (e_ctrl->eeprom_lib.func_tbl &&
e_ctrl->eeprom_params.is_supported) {
if (e_ctrl->eeprom_lib.func_tbl->get_calibration_items != NULL)
e_ctrl->eeprom_lib.func_tbl->get_calibration_items(e_ctrl);
*is_insensor = e_ctrl->eeprom_data.items.is_insensor;
}
}
break;
case EEPROM_GET_ISOIS_CALIB:{
int32_t *is_ois = (int32_t *)data;
if (e_ctrl->eeprom_lib.func_tbl &&
e_ctrl->eeprom_params.is_supported) {
if (e_ctrl->eeprom_lib.func_tbl->get_calibration_items != NULL)
e_ctrl->eeprom_lib.func_tbl->get_calibration_items(e_ctrl);
*is_ois = e_ctrl->eeprom_data.items.is_ois;
}
}
break;
case EEPROM_GET_FORMATTED_DATA:
rc = eeprom_get_formatted_data(e_ctrl, data);
break;
case EEPROM_GET_RAW_DATA:
rc = eeprom_get_raw_data(e_ctrl, data);
break;
case EEPROM_GET_OIS_RAW_DATA:
rc = eeprom_get_ois_raw_data(e_ctrl, data);
break;
case EEPROM_GET_WB_GRGB:
if (e_ctrl->eeprom_data.wbc.gr_over_gb < 1)
*(float*)data = 1 / e_ctrl->eeprom_data.wbc.gr_over_gb;
else
*(float*)data = e_ctrl->eeprom_data.wbc.gr_over_gb;
break;
case EEPROM_GET_WB_CAL:
if (!e_ctrl->eeprom_data.items.is_insensor)
*(wbcalib_data_t**)data = &e_ctrl->eeprom_wbc_factor;
break;
case EEPROM_GET_ISDPC_CALIB:{
int32_t *is_dpc = (int32_t *)data;
if (e_ctrl->eeprom_lib.func_tbl) {
if (e_ctrl->eeprom_lib.func_tbl->get_calibration_items != NULL){
e_ctrl->eeprom_lib.func_tbl->get_calibration_items(e_ctrl);
*is_dpc = e_ctrl->eeprom_data.items.is_dpc;
}
}
break;
}
case EEPROM_SET_CALIBRATE_DUALCAM_PARAM: {
cam_related_system_calibration_data_t *dual_data = (cam_related_system_calibration_data_t *)data;
rc = eeprom_do_dualcam_data_calibration(e_ctrl, dual_data);
break;
}
case EEPROM_DUMP_CALIB_DATA:
{
sensor_chromatix_params_t* chromatix_params;
chromatix_params = (sensor_chromatix_params_t*) data;
eeprom_dbg_data_dump(e_ctrl, chromatix_params, EEPROM_DUMP_CALIB);
}
break;
case EEPROM_INIT:
rc = eeprom_init(e_ctrl);
break;
}
return rc;
}
在 eeprom 头文件中,包含了该 eeprom 具体的参数,
注意,调试的时候一定要结合 eeprom datasheet,及 otp 表格 。
@ mm-camera/mm-camera2/media-controller/modules/sensors/eeprom/libs/rohm_brcg064gwz_3/rohm_brcg064gwz_3_eeprom.h
#define WB_OFFSET 272 // awb 白平衡数据 偏移地址
#define AF_OFFSET 278 // af 对焦数据 偏移地址
#define MESH_HWROLLOFF_SIZE (17*13)
#define LSC_R_OFFSET 512 // LSC Red 数据 偏移地址
#define LSC_GR_OFFSET (LSC_R_OFFSET+MESH_HWROLLOFF_SIZE*2) // LSC GR 数据 偏移地址
#define LSC_GB_OFFSET (LSC_GR_OFFSET+MESH_HWROLLOFF_SIZE*2) // LSC GB 数据 偏移地址
#define LSC_B_OFFSET (LSC_GB_OFFSET+MESH_HWROLLOFF_SIZE*2) // LSC Blue 数据 偏移地址
#define PDGAIN_OFFSET 2816
#define DCC_OFFSET 3706
#define PDGAIN_WITDH 17
#define PDGAIN_HEIGHT 13
#define PDGAIN_LENGTH2D (PDGAIN_HEIGHT * PDGAIN_WITDH)
#define QVALUE 1024.0
#define PAGE_EMPTY 0
#define PAGE_NOT_EMPTY 1
#define MAX_EMPTY_BYTES 8
#define FAR_MARGIN (-0.074)
#define NEAR_MARGIN (0.245)
static unsigned int datapresent = 0;
void brcg064gwz_3_get_calibration_items(void *e_ctrl);
static void brcg064gwz_3_format_calibration_data(void *e_ctrl);
static eeprom_lib_func_t brcg064gwz_3_lib_func_ptr = {
// 1. 该eeprom 对应的数据处理函数
.get_calibration_items = brcg064gwz_3_get_calibration_items,
.format_calibration_data = brcg064gwz_3_format_calibration_data,
.do_af_calibration = eeprom_autofocus_calibration,
.do_wbc_calibration = eeprom_whitebalance_calibration,
.do_lsc_calibration = eeprom_lensshading_calibration,
// 2. eeprom 上下电时序
.eeprom_info =
{
.power_setting_array =
{
.power_setting_a =
{
{
.seq_type = CAMERA_POW_SEQ_VREG,
.seq_val = CAMERA_VIO,
.config_val = 0,
.delay = 0,
},
},
.size = 1,
.power_down_setting_a =
{
{
.seq_type = CAMERA_POW_SEQ_VREG,
.seq_val = CAMERA_VIO,
.config_val = 0,
.delay = 0,
},
},
.size_down = 1,
},
// 3. eeprom 地址映射,有此地址比较复杂的,会在这里配置所有的地址
.i2c_freq_mode = SENSOR_I2C_MODE_FAST,
.mem_map_array =
{
.memory_map =
{
{
.slave_addr = 0xa0,
.mem_settings =
{
{ 0x0, CAMERA_I2C_WORD_ADDR,
3840, CAMERA_I2C_BYTE_DATA, CAMERA_I2C_OP_READ, 0 },
},
.memory_map_size = 1,
},
},
.size_map_array = 1,
},
},
};
在该文件中,主要是就是对数据的转换,将 otp 中的数据,转化为实际能用的数据。
不同的芯片转换规则不一样,具体的转换规则,见eeprom datasheet.
static void brcg064gwz_3_format_calibration_data(void *e_ctrl) {
sensor_eeprom_data_t * ctrl = (sensor_eeprom_data_t *)e_ctrl;
unsigned char *buffer = ctrl->eeprom_params.buffer;
unsigned short crc = 0;
datapresent = 0;
SHIGH("OTP: total bytes: %d",ctrl->eeprom_params.num_bytes);
datapresent = 1;
brcg064gwz_3_format_pdafdata(ctrl);
brcg064gwz_3_format_wbdata(ctrl);
brcg064gwz_3_format_afdata(ctrl);
brcg064gwz_3_format_lscdata(ctrl);
}
msm-qcom-daemon
中下发 CFG_EEPROM_INIT
命令后,会
static int msm_eeprom_config(struct msm_eeprom_ctrl_t *e_ctrl, void *argp)
{
CDBG("%s E\n", __func__);
switch (cdata->cfgtype) {
case CFG_EEPROM_INIT:
if (e_ctrl->cal_data.num_data == 0) {
rc = eeprom_init_config(e_ctrl, argp);
}
break;
}
在 eeprom_init_config 对eerpom 做上电初始化操作:
主要工作如下:
power_setting_array
e_ctrl->cal_data.mapdata
中static int eeprom_init_config(struct msm_eeprom_ctrl_t *e_ctrl, void *argp)
{
power_setting_array = kzalloc(sizeof(struct msm_sensor_power_setting_array), GFP_KERNEL);
memory_map_arr = kzalloc(sizeof(struct msm_eeprom_memory_map_array), GFP_KERNEL);
// 1. 获取上层下发的上电时序,保存在 power_setting_array 中,对应在lib.h 中的 power_setting_array
copy_from_user(power_setting_array,(void __user *)cdata->cfg.eeprom_info.power_setting_array,
sizeof(struct msm_sensor_power_setting_array));
CDBG("%s:%d Size of power setting array: %d\n", __func__, __LINE__, power_setting_array->size);
// 2. 获取上层下发的地址映射表,对应在lib.h 中的 mem_map_array
copy_from_user(memory_map_arr,(void __user *)cdata->cfg.eeprom_info.mem_map_array,
sizeof(struct msm_eeprom_memory_map_array));
power_info = &(e_ctrl->eboard_info->power_info);
power_info->power_setting = power_setting_array->power_setting_a;
power_info->power_down_setting = power_setting_array->power_down_setting_a;
power_info->power_setting_size = power_setting_array->size;
power_info->power_down_setting_size = power_setting_array->size_down;
if (e_ctrl->i2c_client.cci_client) {
e_ctrl->i2c_client.cci_client->i2c_freq_mode = cdata->cfg.eeprom_info.i2c_freq_mode;
}
// 3. 上电
/* Fill vreg power info and power up here */
rc = msm_eeprom_power_up(e_ctrl, power_info);
=============>
+ @ msm-4.14/drivers/media/platform/msm/camera_v2/sensor/eeprom/msm_eeprom.c
+ msm_camera_fill_vreg_params( power_info->cam_vreg, power_info->num_vreg,
+ power_info->power_setting, power_info->power_setting_size);
+ msm_camera_fill_vreg_params( power_info->cam_vreg, power_info->num_vreg,
+ power_info->power_down_setting, power_info->power_down_setting_size);
+ msm_camera_power_up(power_info, e_ctrl->eeprom_device_type, &e_ctrl->i2c_client);
<=============
// 4. 对eeprom lib.h 中配置的地址进行 循环映射,将内容保存在 e_ctrl->cal_data.mapdata 中
rc = eeprom_parse_memory_map(e_ctrl, memory_map_arr);
=============>
+ memptr = e_ctrl->cal_data.mapdata;
+ // 循环遍历
+ for (j = 0; j < eeprom_map_array->msm_size_of_max_mappings; j++) {
+ for (i = 0; i < eeprom_map->memory_map_size; i++) {
+ e_ctrl->i2c_client.i2c_func_tbl->i2c_read_seq(&(e_ctrl->i2c_client),
+ eeprom_map->mem_settings[i].reg_addr, memptr, eeprom_map->mem_settings[i].reg_data);
+ }
+ }
+ memptr = e_ctrl->cal_data.mapdata;
+ for (i = 0; i < e_ctrl->cal_data.num_data; i++) // 打印 OTP 内容
+ CDBG("memory_data[%d] = 0x%X\n", i, memptr[i]);
<=============
// 5. 下电
rc = msm_camera_power_down(power_info, e_ctrl->eeprom_device_type, &e_ctrl->i2c_client);
return rc;
}
@ msm-4.14/drivers/media/platform/msm/camera_v2/sensor/eeprom/msm_eeprom.c
case CFG_EEPROM_READ_CAL_DATA:
rc = eeprom_config_read_cal_data(e_ctrl, cdata); break;
在 eeprom_config_read_cal_data()
中,主要是获取 cal_data.mapdata
中的保存的 otp 数据
static int eeprom_config_read_cal_data(struct msm_eeprom_ctrl_t *e_ctrl, struct msm_eeprom_cfg_data *cdata)
{
rc = copy_to_user((void __user *)cdata->cfg.read_data.dbuffer,
e_ctrl->cal_data.mapdata, cdata->cfg.read_data.num_bytes);
}
这样,就将kernel 中的 e_ctrl->cal_data.mapdata 中的 otp 数据通过copy_to_user 传递到了vendor 中了。
总结 eeprom 数据初始化过程为:
mm-qcamera-daemon
进程启动时,在main
函数中调用module_sensor_init
来初化Sensor,
从而调用到 module_sensor_init_eeprom
函数,在该函数中下加载 eeprom lib 为库,
下发 EEPROM_INIT
参数给eeprom上电,并读取eeprom 保存起来,
当真正要使用数据时,下发EEPROM_READ_DATA
参数来获取 eeprom 数据。