ARTICLE · 988121
安卓摄像头是如何识别sensor id的?
今晚阅读了MTK平台sensor驱动代码,觉得收获颇多,记录下来。
这里以MTK平台的OV48BSensor驱动为例,这套 V4L2 imgsensor 的流程可以分成两条:
1. Sensor 识别链路:给 sensor 上电 → 读硬件 Sensor ID → 和驱动预设 ID 比较 → 确定是哪颗摄像头。 2. CAM_CAL /EEPROM匹配链路:拿已经确定的 Sensor ID → 查 g_oplusCamCalList[]→ 得到 EEPROM I2C 地址、读取函数、EEPROM 大小 → 再读取校准数据。
这两条链通过 Sensor ID 串起来,但不是同一件事。
一、摄像头 Sensor ID 是怎么识别出来的
• 完整调用链是:
Linux I2C Core ↓imgsensor_probe() ↓search_sensor(ctx) ↓遍历候选 Sensor 驱动 ↓ctx->subdrv = subdrvs[i] ↓adaptor_hw_power_on(ctx) ↓subdrv_call(ctx, init_ctx, ...) ↓subdrv_call(ctx, get_id, &sensor_id) ↓当前 Sensor 驱动的 ops->get_id ↓OV48B:.get_id = common_get_imgsensor_id ↓common_get_imgsensor_id() ↓遍历可能的 Sensor I2C 地址 ↓I2C 读取 Sensor ID 寄存器 ↓拼出实际 Sensor ID ↓和驱动预设 Sensor ID 比较 ↓匹配成功 ↓确定当前摄像头最上层入口:imgsensor_probe()
文件:
vendor/mediatek/kernel_modules/└── mtkcam/ └── imgsensor/ └── src-v4l2/ └── adaptor-drv.c完整路径:
/home/ubuntu/Desktop/vnd/vendor/mediatek/kernel_modules/mtkcam/imgsensor/src-v4l2/adaptor-drv.cgrep 到:
422: static int search_sensor(struct adaptor_ctx *ctx)1441: static int imgsensor_probe(struct i2c_client *client)1533: ret = search_sensor(ctx);1762: .probe_new = imgsensor_probe,所以最上层关系:
.probe_new = imgsensor_probe,说明 Linux I2C core 在匹配这个 imgsensor I2C driver 之后,会调用:
imgsensor_probe(struct i2c_client *client)然后在 imgsensor_probe() 里面:
ret = search_sensor(ctx);正式开始寻找当前到底是哪颗 sensor。
因此:
I2C driver probe ↓imgsensor_probe() ↓search_sensor()二、search_sensor():真正负责“找哪颗 Sensor”
文件仍然是:
mtkcam/imgsensor/src-v4l2/adaptor-drv.c整个 Sensor 识别流程的核心调度函数。
staticintsearch_sensor(struct adaptor_ctx *ctx)它首先会从设备树读取:
of_property_read_string_array(ctx->dev->of_node,"sensor-names", ...);以及:
of_property_read_string(ctx->dev->of_node,"subdrv-name", &of_subdrv_name);也就是说设备树可以告诉驱动:
这个位置可能有哪些 Sensor。
如果设备树配置了 sensor-names,它就在imgsensor_subdrvs[]中根据名字找到对应的:struct subdrv_entry 如果设备树没限定,就可能使用整个:imgsensor_subdrvs[]候选表。所以此时:
设备树 sensor-names ↓imgsensor_subdrvs[] ↓候选 Sensor driver 列表三、开始逐个尝试 Sensor
search_sensor() 后面:
for (i = 0; i < subdrvs_cnt; i++) { u32 sensor_id = 0xffffffff; ctx->subdrv = subdrvs[i];这里非常关键。假如有:
候选0:IMX709候选1:IMX766候选2:OV48B...那么每次:
ctx->subdrv = subdrvs[i];就是:
当前先假设这颗 sensor 是第 i 个驱动,然后尝试读它的 ID。
例如轮到 OV48B:
ctx->subdrv ↓ov48b_mipi_raw_entry四、先给 Sensor 上电
接着:
adaptor_hw_power_on(ctx);为什么要先上电?
因为你还没给 sensor:
MCLKAVDDDVDDDOVDDRESET这些东西的话,I2C 当然读不到 Sensor ID。
OV48B 自己定义了 power sequence:
staticstructsubdrv_pw_seq_entrypw_seq[] = { {HW_ID_MCLK, 24, 0}, {HW_ID_RST, 0, 1}, {HW_ID_MCLK_DRIVING_CURRENT, 8, 0}, {HW_ID_DOVDD, 1800000, 0}, {HW_ID_AVDD, 2800000, 0}, {HW_ID_DVDD, 1090000, 5}, {HW_ID_RST, 1, 5},};然后在:
ov48b_mipi_raw_entry里:
.pw_seq = pw_seq,.pw_seq_cnt = ARRAY_SIZE(pw_seq),因此:
search_sensor() ↓adaptor_hw_power_on() ↓根据 pw_seq ↓MCLK / RESET / DOVDD / AVDD / DVDD ↓Sensor 可以进行 I2C 通信OV48B 的 subdrv_entry 确实把 Sensor 名、ID、上电序列和 ops 绑定在了一起。
五、初始化 Sensor 上下文 init_ctx
然后:
subdrv_call(ctx, init_ctx, ctx->i2c_client, ctx->subctx.i2c_write_id);对于 OV48B:
staticstructsubdrv_opsops = { .get_id = common_get_imgsensor_id, .init_ctx = init_ctx, ...};所以这里:
subdrv_call(ctx, init_ctx) ↓OV48B ops.init_ctx ↓init_ctx()OV48B 的 init_ctx():
staticintinit_ctx(struct subdrv_ctx *ctx,struct i2c_client *i2c_client, u8 i2c_write_id){memcpy(&(ctx->s_ctx), &static_ctx,sizeof(struct subdrv_static_ctx)); subdrv_ctx_init(ctx); ctx->i2c_client = i2c_client; ctx->i2c_write_id = i2c_write_id;return0;}也就是说最重要的一步:
memcpy(&(ctx->s_ctx), &static_ctx, ...);把 OV48B 的静态配置复制进:
ctx->s_ctx这样后面的通用函数:
common_get_imgsensor_id()根本不需要知道自己现在是 OV48B、IMX766 还是别的 Sensor。
它只需要看:
ctx->s_ctx就行。
六、OV48B 的 Sensor ID 配置在哪里?
就在:
mtkcam/imgsensor/src-v4l2/common/ov48b_mipi_raw/└── ov48bmipiraw_Sensor.c完整路径:
/home/ubuntu/Desktop/vnd/vendor/mediatek/kernel_modules/mtkcam/imgsensor/src-v4l2/common/ov48b_mipi_raw/ov48bmipiraw_Sensor.c里面:
staticstructsubdrv_static_ctxstatic_ctx = { .sensor_id = OV48B_SENSOR_ID, .reg_addr_sensor_id = {0x300A,0x300B,0x300C }, .i2c_addr_table = {0x6D,0x20,0xFF }, ...};也就是说驱动已经提前告诉 common 层三件事:
预期 Sensor ID:OV48B_SENSOR_IDSensor ID 寄存器:0x300A0x300B0x300C可能的 I2C 地址:0x6D0x200xFF = 地址表结束这些配置就在 OV48B 的 static_ctx 中。
特别注意:
.sensor_id = OV48B_SENSOR_ID不是从硬件读出来的。
它是:
驱动预先定义的“正确答案”。
而:
.reg_addr_sensor_id才告诉驱动:
去哪些硬件寄存器读取真正的 Sensor ID。
七、真正触发读取 Sensor ID
回到:
search_sensor()最重要的就是:
ret = subdrv_call(ctx, get_id, &sensor_id);这句话就是:
调用当前候选 Sensor 的
get_id()接口。
对于 OV48B,它的 ops 是:
staticstructsubdrv_opsops = { .get_id = common_get_imgsensor_id,因此:
subdrv_call(ctx, get_id, &sensor_id);最终就会执行:
common_get_imgsensor_id(...)关系:
search_sensor() ↓subdrv_call(ctx, get_id, &sensor_id) ↓ctx->subdrv ↓ov48b_mipi_raw_entry ↓.ops ↓ops.get_id ↓common_get_imgsensor_id八、真正读硬件 Sensor ID:common_get_imgsensor_id()
代码在:
/home/ubuntu/Desktop/vnd/vendor/mediatek/kernel_modules/mtkcam/imgsensor/src-v4l2/adaptor-subdrv-ctrl.c函数:
intcommon_get_imgsensor_id(struct subdrv_ctx *ctx, u32 *sensor_id)首先:
u32 addr_h = ctx->s_ctx.reg_addr_sensor_id.addr[0];u32 addr_l = ctx->s_ctx.reg_addr_sensor_id.addr[1];u32 addr_ll = ctx->s_ctx.reg_addr_sensor_id.addr[2];因为 init_ctx() 已经把 OV48B 的 static_ctx 复制过来了,所以实际上:
addr_h = 0x300Aaddr_l = 0x300Baddr_ll = 0x300C九、遍历 I2C 地址
代码:
while (ctx->s_ctx.i2c_addr_table[i] != 0xFF) {OV48B:
.i2c_addr_table = {0x6D, 0x20, 0xFF},所以执行:
第一次:i2c_write_id = 0x6D ↓尝试读 ID 两次失败 ↓第二次:i2c_write_id = 0x20 ↓尝试读 ID 两次失败 ↓0xFF ↓结束当前地址通过:
ctx->i2c_write_id = ctx->s_ctx.i2c_addr_table[i];保存。
十、I2C 真正读取 ID 寄存器
核心:
*sensor_id = (subdrv_i2c_rd_u8(ctx, addr_h) << 8) | subdrv_i2c_rd_u8(ctx, addr_l);对于 OV48B:
I2C Read 0x300AI2C Read 0x300B如果还有第三个寄存器:
if (addr_ll) *sensor_id = ((*sensor_id) << 8) | subdrv_i2c_rd_u8(ctx, addr_ll);因此 OV48B 最终相当于:
sensor_id = (read(0x300A) << 16) | (read(0x300B) << 8) | read(0x300C);所以这里才是真正:
通过 I2C 从 sensor 硬件寄存器读出芯片 ID。
十一、比较实际 ID 和预设 ID
之后:
if (*sensor_id == ctx->s_ctx.sensor_id)return ERROR_NONE;左边:
*sensor_id是:
刚刚 I2C 从 sensor 读出来的真实 ID。
右边:
ctx->s_ctx.sensor_id是:
OV48B_SENSOR_ID也就是驱动预设的 ID。
所以:
硬件实际 ID ↓例如 0xXXXXXX ↓ 比较 ↑OV48B_SENSOR_ID驱动预期值相同:
return ERROR_NONE;说明:
这颗硬件确实是 OV48B。
十二、search_sensor() 确认找到 Sensor
回到:
search_sensor():
ret = subdrv_call(ctx, get_id, &sensor_id);adaptor_hw_power_off(ctx);if (!ret) { dev_info(ctx->dev,"sensor %s found\n", ctx->subdrv->name); ...return0;}因此:
common_get_imgsensor_id() ↓ID匹配 ↓ERROR_NONE ↓search_sensor() ↓!ret 成立 ↓sensor ov48b_mipi_raw found至此:
Sensor 识别完成。
十三、Sensor ID 识别流程完整图
把前面全部压成一张图:
设备树sensor-names / subdrv-name ↓imgsensor_probe() │ └─ adaptor-drv.c ↓search_sensor() ↓遍历 imgsensor_subdrvs[] ↓ctx->subdrv = 当前候选Sensor ↓adaptor_hw_power_on() ↓subdrv_call(init_ctx) ↓OV48B init_ctx() ↓static_ctx → ctx->s_ctx │ ├─ sensor_id = OV48B_SENSOR_ID ├─ ID寄存器 = 0x300A/0x300B/0x300C └─ I2C地址 = 0x6D/0x20 ↓subdrv_call(get_id, &sensor_id) ↓ops.get_id ↓common_get_imgsensor_id() │ ├─ 尝试 I2C 0x6D │ ├─ 尝试 I2C 0x20 │ ↓subdrv_i2c_rd_u8() ↓读取:0x300A0x300B0x300C ↓拼成实际 Sensor ID ↓实际ID == OV48B_SENSOR_ID ? │ YES ↓ERROR_NONE ↓search_sensor() ↓sensor ov48b_mipi_raw found十四、识别 Sensor 后,EEPROM 配置怎么找?
这是第二条链。
一定要注意:
common_get_imgsensor_id()负责识别 Sensor,不负责 CAM_CAL EEPROM 配置表匹配。
EEPROM 配置是在另一套 cam_cal 代码里完成的。
代码路径在
vendor/mediatek/kernel_modules/mtkcam/cam_cal/src_v4l2/custom/cam_cal_list.c相关代码
unsignedintcam_cal_get_sensor_list(struct stCAM_CAL_LIST_STRUCT **ppCamcalList){if (ppCamcalList == NULL)return1;#ifndef OPLUS_FEATURE_CAMERA_COMMON *ppCamcalList = &g_camCalList[0];#else *ppCamcalList = &g_oplusCamCalList[0];#endifreturn0;}因为你在Kbuild 里:
subdir-ccflags-y += -D OPLUS_FEATURE_CAMERA_COMMON因此:
OPLUS_FEATURE_CAMERA_COMMON已经定义。
所以:
#ifndef OPLUS_FEATURE_CAMERA_COMMON不成立,最终进入:
#else执行:
*ppCamcalList = &g_oplusCamCalList[0];也就是说:
当前 OPLUS 项目使用的是
g_oplusCamCalList,而不是 MTK 默认的g_camCalList。
十五、OPLUS EEPROM 配置表
这里是oplus的eeprom配置表:
structstCAM_CAL_LIST_STRUCTg_oplusCamCalList[] = { {IMX709LUNA_SENSOR_ID,0xA8, Common_read_region, MAX_EEPROM_SIZE_16K}, {IMX766LUNA_SENSOR_ID,0xA0, Common_read_region, MAX_EEPROM_SIZE_32K}, {IMX800LUNA_SENSOR_ID,0xA0, Common_read_region, MAX_EEPROM_SIZE_32K}, {S5KJN1LUNA_SENSOR_ID,0xA2, Common_read_region, MAX_EEPROM_SIZE_32K}, {0, 0, 0}};每一项本质上就是:
{ Sensor ID, EEPROM I2C地址, EEPROM读取函数, EEPROM最大容量}例如:
{ IMX766LUNA_SENSOR_ID,0xA0, Common_read_region, MAX_EEPROM_SIZE_32K}表示:
如果当前 Sensor ID =IMX766LUNA_SENSOR_ID那么它的 CAM_CAL EEPROM 配置:EEPROM I2C write ID = 0xA0读取函数 = Common_read_region最大 EEPROM 大小 = 32K十六、这里为什么还要 Sensor ID?
因为系统可能有很多 camera:
主摄 IMX766前摄 IMX709广角 IMX800副摄 S5KJN1...每颗 Sensor 的 EEPROM:
I2C 地址容量布局读取方式可能不一样。
所以不能写死一个 EEPROM 配置。
而是:
当前 Sensor ID ↓查 CAM_CAL LIST ↓找到对应项 ↓知道该 Sensor 的 EEPROM 怎么读因此 Sensor ID 在这里就成为:
CAM_CAL 配置表的 key。
十七、EEPROM 匹配链路
完整可以理解成:
前面已经识别出当前 Sensor ↓已经知道 Sensor ID ↓CAM_CAL driver ↓cam_cal_get_sensor_list() ↓因为定义:OPLUS_FEATURE_CAMERA_COMMON ↓返回:g_oplusCamCalList ↓调用者遍历这个数组 ↓拿当前 Sensor ID ↓和数组里的 SENSOR_ID 比较 ↓找到对应项 ↓得到 EEPROM:I2C地址读取函数最大容量 ↓Common_read_region() ↓读取 EEPROM ↓获取 OTP / Calibration 数据十八、两条链最终怎么串起来?
CAMERA SENSOR │ │ I2C ↓ 读取 Sensor ID │ ↓ 得到 SENSOR_ID │ ┌───────┴────────┐ │ │ │ │ Sensor Driver CAM_CAL Driver │ │ │ ↓ │ g_oplusCamCalList │ │ │ 根据 SENSOR_ID 查表 │ ↓ │ EEPROM I2C地址 │ EEPROM大小 │ EEPROM读取函数 │ │ └────────┐ │ ↓ ↓ 当前摄像头 + 对应EEPROM配置所以准确说:
Sensor ID承担了两个作用:
第一:确认当前物理硬件到底是哪颗 Sensor第二:作为 key 去找到这颗 Sensor 对应的EEPROM / CAM_CAL 配置最后把整个流程压成一条
设备树指定候选 Sensor ↓imgsensor_probe() ↓search_sensor() ↓选择候选 subdrv ↓给 Sensor 上电 ↓init_ctx() ↓加载 Sensor static_ctx ↓subdrv_call(get_id) ↓common_get_imgsensor_id() ↓根据 i2c_addr_table 尝试 I2C 地址 ↓读取 reg_addr_sensor_id ↓拼出实际 Sensor ID ↓和 static_ctx.sensor_id 比较 ↓确认具体 Sensor ↓拿 SENSOR_ID ↓cam_cal_get_sensor_list() ↓OPLUS_FEATURE_CAMERA_COMMON 已开启 ↓选择 g_oplusCamCalList ↓根据 SENSOR_ID 查表 ↓得到 EEPROM I2C 地址 ↓得到 EEPROM 读取函数 ↓得到 EEPROM 最大容量 ↓Common_read_region() ↓读取 EEPROM / OTP Calibration 数据最重要的思想就是:
imgsensor 负责“确认我是哪颗 Sensor”;cam_cal 负责“知道我是哪颗 Sensor 后,找到我的EEPROM应该怎么读”。
两边通过:SENSOR_ID关联起来。