夜雨聆风学习资料网

ARTICLE · 988121

安卓摄像头是如何识别sensor id的?

安卓摄像头是如何识别sensor id的?

今晚阅读了MTK平台sensor驱动代码,觉得收获颇多,记录下来。

这里以MTK平台的OV48BSensor驱动为例,这套 V4L2 imgsensor 的流程可以分成两条:

  1. 1. Sensor 识别链路:给 sensor 上电 → 读硬件 Sensor ID → 和驱动预设 ID 比较 → 确定是哪颗摄像头。
  2. 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.c

grep 到:

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关联起来。

相关学习资料