
简介这份资源是面向嵌入式Linux驱动开发者的Sony IMX335传感器驱动适配代码针对海思Hi3559高性能视频处理平台定制可解决IMX335在Hi3559系统上的初始化、时序控制与图像数据采集问题适用于安防监控、智能分析、工业相机等场景的底层开发与移植调试。压缩包共6个文件约362KB包含2个C源文件、2个编译生成的.o目标文件、1个Makefile构建脚本和1个头文件覆盖传感器控制、CMOS驱动逻辑与编译配置结构紧凑便于直接集成到V4L2或MIPI CSI-2框架中。目前已有1072人学习下载说明该适配方案在同类平台移植中具有较高参考价值。读者可据此快速理解IMX335在Hi3559上的寄存器配置、接口协议与电源管理思路并借助已验证的驱动代码缩短调试周期降低兼容性排查成本。1. 把 IMX335 接到 HI3559 上一条 sensor 驱动适配的完整路径手里有一块 HI3559A 的开发板一颗 Sony IMX335 的 sensor 模组MIPI 排线接好、上电、跑起 sample结果/dev下什么都没有cat /proc/umap/isp里 sensor 那一栏是空的——这是很多人第一次做 sony_imx335 driver 驱动适配 hi3559 平台时遇到的场景。问题不在硬件而在于 HI3559 的 ISP 框架要求 sensor 必须有一份符合它自己接口规范的驱动Sony 原厂给的寄存器手册和 Linux mainline 的 imx335.c 都不能直接拿来用。这篇笔记讲的就是怎么从零把这条链路打通I2C 通信、寄存器序列、MIPI 时序、VI/ISP 绑定每一步都会给出可复现的代码和参数也会说清楚哪些地方最容易翻车。适合正在做海思平台 sensor 移植的驱动工程师也适合想理解 ISP sensor 驱动分层的人。2. HI3559 sensor 驱动的分层结构与 IMX335 的接入点2.1 为什么不能直接用 mainline 的 imx335 驱动Linux mainline 里的drivers/media/i2c/imx335.c是给 V4L2 框架写的它通过v4l2_subdev暴露 pad、format、ctrl走的是标准 media controller 那套。HI3559 的 SDK 虽然也跑 Linux但它的 ISP 和 VI 模块并不走 V4L2 的 media pipeline而是海思自己的一套combo_devsensor注册机制。sensor 驱动要实现的是一组ot_isp_sensor_register相关的回调包括曝光、增益、帧率、镜像、寄存器读写而不是 V4L2 的 ops。换句话说mainline 驱动里那些v4l2_ctrl_handler、media_entity在海思这边基本用不上能复用的只有 I2C 读写和一部分寄存器表。我一般会把 mainline 的 imx335.c 当作寄存器参考但驱动骨架完全按海思 sample 里的sensor_xxx.c来写。海思 SDK 里通常自带几款 sensor 的驱动源码比如sensor_imx290.c、sensor_ov5640.c这些才是真正该抄的模板。2.2 驱动需要实现的四组接口海思的 sensor 驱动核心是四组东西缺一不可第一组是 I2C 通信层。海思 SDK 提供了sensor_i2c.c里的sensor_write_register/sensor_read_register但你需要填一个sensor_i2c_info结构指定 I2C 总线号、设备地址、寄存器位宽、数据位宽。IMX335 的 I2C 地址是 0x1A7 位寄存器地址 16 位数据 8 位这组参数写错就会导致 probe 阶段读 chip id 失败。第二组是寄存器初始化序列。IMX335 上电后需要按顺序写几百个寄存器才能出图包括 PLL 配置、MIPI lane 设置、曝光默认值、增益默认值。海思的框架要求你把这些序列放在一个sensor_regs数组里在sensor_init回调里一次性写下去。第三组是 ISP 回调。海思 ISP 会周期性地调用你的sensor_get_exposure、sensor_set_exposure、sensor_get_gain、sensor_set_gain还有sensor_set_fps、sensor_set_mirror_flip。这些回调里你要把海思传进来的物理量比如曝光时间以微秒为单位换算成 IMX335 的寄存器值。第四组是 MIPI 时序参数。这部分不在驱动代码里而在sample_comm_isp.c或sample_comm_vi.c的combo_dev_attr结构里包括 lane 数、data rate、clock 极性。驱动和这部分必须对齐否则 I2C 通了但 VI 抓不到数据。2.3 最小可跑的驱动骨架下面是一个裁剪过的驱动骨架只保留最关键的注册和回调能让你先跑通 probe#include sensor_common.h static ot_isp_sensor_register_info g_sensor_register_info { .sensor_type OT_ISP_SENSOR_IMX335, .i2c_dev 0, /* I2C 总线号按实际硬件改 */ .i2c_addr 0x1A, /* IMX335 7 位地址 */ .reg_addr_width 16, /* 寄存器地址 16 位 */ .data_width 8, /* 数据 8 位 */ }; static int imx335_probe(ot_isp_sensor *sensor) { unsigned int chip_id 0; /* IMX335 chip id 在 0x3000 和 0x3001 */ sensor_read_register(0x3000, chip_id); if (chip_id ! 0x33) { dprintf(imx335 chip id mismatch: 0x%x\n, chip_id); return -1; } return 0; } static int imx335_init(ot_isp_sensor *sensor) { int i; for (i 0; i ARRAY_SIZE(imx335_init_regs); i) { sensor_write_register(imx335_init_regs[i].addr, imx335_init_regs[i].data); } return 0; } static int imx335_set_exposure(ot_isp_sensor *sensor, unsigned int exp_us) { unsigned int lines; /* IMX335 一行约 22.2us全分辨率 30fps 时 */ lines exp_us * 1000 / 22200; sensor_write_register(0x3058, lines 0xFF); sensor_write_register(0x3059, (lines 8) 0xFF); sensor_write_register(0x305A, (lines 16) 0x0F); return 0; } static ot_isp_sensor_ops g_sensor_ops { .probe imx335_probe, .init imx335_init, .set_exposure imx335_set_exposure, /* 其余回调按需补全 */ }; int imx335_register(void) { return ot_isp_sensor_register(g_sensor_register_info, g_sensor_ops); }这段代码里sensor_read_register和sensor_write_register是海思 SDK 提供的封装你不需要自己写 I2C 传输。imx335_init_regs是一个结构体数组每个元素包含 16 位地址和 8 位数据。set_exposure里的换算系数 22200 是 IMX335 在 2592x1944 分辨率、30fps 下的行时间单位是纳秒这个值必须和你的 MIPI 配置一致否则曝光会偏。提示chip id 读出来是 0x33 还是 0x335 取决于你读几位IMX335 的 0x3000 返回 0x330x3001 返回 0x35合起来是 0x335。有些 sample 只读 0x3000 判断 0x33 就够了。3. 寄存器序列与 MIPI 时序让 IMX335 真正出图3.1 初始化序列的四个关键段IMX335 的初始化寄存器有几百个但真正决定能不能出图的是四段PLL 配置、MIPI 全局设置、曝光/增益默认值、输出格式。PLL 配置决定了 sensor 内部时钟写错会导致 MIPI 没有时钟输出。MIPI 全局设置包括 lane 数、data rate、LPDT 使能。曝光和增益默认值决定第一帧的亮度。输出格式决定 RAW10 还是 RAW12。我一般会从 Sony 的寄存器手册里抄一份推荐序列然后按海思 sample 的格式转成数组。下面是一个片段static sensor_reg imx335_init_regs[] { {0x3000, 0x01}, /* 进入 standby */ {0x3002, 0x00}, /* 解除 reset */ {0x300C, 0x00}, /* 关闭 master mode */ /* PLL 配置24MHz 输入输出 1188Mbps/lane */ {0x3030, 0x02}, {0x3031, 0x00}, {0x3032, 0x00}, /* MIPI 全局4 laneRAW10 */ {0x3040, 0x00}, {0x3041, 0x00}, {0x3042, 0x00}, /* 曝光默认0x0A00 行 */ {0x3058, 0x00}, {0x3059, 0x0A}, {0x305A, 0x00}, /* 增益默认0dB */ {0x30E8, 0x00}, {0x30E9, 0x00}, /* 退出 standby */ {0x3000, 0x00}, };这段序列里0x3030到0x3032是 PLL 的倍频参数具体值取决于你的输入时钟是 24MHz 还是 27MHz。如果你的模组用的是 27MHz 晶振这三个值要改否则 MIPI 时钟频率不对VI 会报 sync 丢失。0x3040是 lane 数配置0x00 表示 4 lane如果你只接了 2 lane这里要改成 0x01。3.2 MIPI 时序参数在 combo_dev_attr 里的对应关系驱动里写的是 sensor 内部寄存器但 MIPI 的物理层参数是在sample_comm_vi.c的combo_dev_attr里配的。这两者必须对齐。下面是一个 4 lane、RAW10、1188Mbps 的配置combo_dev_attr_t g_mipi_attr { .input_mode INPUT_MODE_MIPI, .data_rate MIPI_DATA_RATE_X1, .img_rect {0, 0, 2592, 1944}, .mipi_attr { .lane_id {0, 1, 2, 3}, /* 4 lane 映射 */ .output_mode OUTPUT_MODE_RAW10, .data_type DATA_TYPE_RAW10, }, };lane_id的顺序必须和硬件走线一致如果 PCB 上 lane 顺序是反的这里要相应调整否则图像会花屏或者完全黑。data_rate选 X1 还是 X2 取决于 sensor 输出的 MIPI 时钟频率IMX335 在 1188Mbps 下用 X1 就够了。img_rect的宽高要和 sensor 输出分辨率一致IMX335 全分辨率是 2592x1944如果你用了裁剪模式这里也要改。3.3 曝光和增益的换算公式海思 ISP 传给驱动的曝光值单位是微秒增益单位是 dB 的定点数。IMX335 的曝光寄存器是 20 位分布在 0x3058、0x3059、0x305A 三个寄存器里。增益寄存器是 0x30E8 和 0x30E9对应的是模拟增益和数字增益的组合。曝光换算/* line_time_ns 是全分辨率下的行时间单位纳秒 */ #define IMX335_LINE_TIME_NS 22200 static int imx335_set_exposure(ot_isp_sensor *sensor, unsigned int exp_us) { unsigned int lines (unsigned long long)exp_us * 1000 / IMX335_LINE_TIME_NS; if (lines 0xFFFFF) lines 0xFFFFF; /* 20 位上限 */ sensor_write_register(0x3058, lines 0xFF); sensor_write_register(0x3059, (lines 8) 0xFF); sensor_write_register(0x305A, (lines 16) 0x0F); return 0; }增益换算稍微麻烦一点IMX335 的增益寄存器 0x30E8 是模拟增益的整数部分0x30E9 是小数部分。海思传进来的增益是 dB需要先转成线性倍数再查表或者按公式算寄存器值。我一般用一张 0dB 到 30dB 的查找表每 0.3dB 一个点这样精度够用。注意曝光行数不能超过 VMAX也就是一帧的总行数。IMX335 在 30fps 下 VMAX 大约是 2250 行如果你设的曝光超过这个值帧率会自动下降这是 sensor 的正常行为不是 bug。4. 避坑与排查I2C 通了但 VI 没数据的五种情况4.1 现象probe 成功但 /proc/umap/isp 里 sensor 为空原因通常是ot_isp_sensor_register的返回值没检查或者注册时机不对。海思的 ISP 初始化在sample_comm_isp.c里sensor 注册必须在 ISP 启动之前完成。如果你把imx335_register()放在 ISP 启动之后调用注册会失败但不会报错。解决方法是把注册调用放到sample_comm_isp_start之前并且检查返回值。另外确认sensor_type填的是OT_ISP_SENSOR_IMX335如果 SDK 里没有这个枚举需要自己在ot_isp_sensor.h里加一个。4.2 现象I2C 能读到 chip id但写寄存器后读回值不对这是 I2C 地址位宽或数据位宽配错导致的。IMX335 的寄存器地址是 16 位但有些模组的 I2C 地址是 0x1A 左移一位后的 0x34。海思的sensor_i2c_info里i2c_addr填的是 7 位地址还是 8 位地址取决于 SDK 版本。我遇到过填 0x1A 读不到、填 0x34 才能读的情况用i2cdetect先扫一下总线确认。另一个可能是 I2C 速率太高。IMX335 支持 400kHz但有些开发板的 I2C 上拉电阻偏大实际跑不到 400kHz。把速率降到 100kHz 试试如果通了就是硬件问题。4.3 现象MIPI 有时钟但 VI 报 sync 丢失先查combo_dev_attr里的lane_id顺序。海思的 lane 映射和硬件走线是强绑定的如果 PCB 上 lane0 接的是 sensor 的 lane3这里就要写成{3, 2, 1, 0}。其次是data_typeIMX335 默认输出 RAW10如果你配成了 RAW12VI 会解析失败。还有一个容易忽略的点是 MIPI 的 clock lane 极性。有些模组的 clock 是差分对反接的需要在mipi_attr里改clk_pol。这个参数在 sample 里默认是 0如果时钟极性反了VI 会一直报 sync 丢失。4.4 现象出图了但颜色不对偏绿或偏紫这是 RAW 格式的 Bayer 顺序配错了。IMX335 默认是 RGGB但海思 ISP 的bayer_format要设成对应的值。如果设成了 BGGR红蓝通道会互换图像偏蓝或偏紫。在sample_comm_isp.c里找到isp_bayer_format这个参数改成OT_ISP_BAYER_RGGB。另外检查一下 AWB 是否使能。如果 AWB 没开图像会偏绿这是 ISP 的默认行为不是 sensor 的问题。4.5 现象曝光调到最大仍然很暗先确认IMX335_LINE_TIME_NS这个系数对不对。如果你改了 MIPI data rate 或者分辨率行时间会变这个系数必须跟着改。比如你把分辨率降到 1080p行时间会变小同样的曝光微秒数对应的行数就变多了图像会变亮。反过来如果你从 1080p 升到全分辨率行时间变大曝光行数变少图像会变暗。另一个可能是增益没生效。IMX335 的增益寄存器写入后需要等一帧才生效如果你在set_gain里写完立刻读回读到的还是旧值。这是正常现象不要以为是写失败。5. 用脚本验证寄存器序列与快速定位时序问题5.1 用 i2cget/i2cset 在用户态验证寄存器在驱动还没跑通之前可以先在用户态用i2cget和i2cset验证 I2C 通信和寄存器序列。这样能把驱动问题和硬件问题分开。下面是一个 bash 脚本用来读 chip id 和写几个关键寄存器#!/bin/bash BUS0 ADDR0x1A # 读 chip id0x3000 应该返回 0x33 i2cset -y $BUS $ADDR 0x30 0x00 ID$(i2cget -y $BUS $ADDR 0x30) echo chip id high: $ID # 写 PLL 寄存器 i2cset -y $BUS $ADDR 0x30 0x30 0x02 i2cset -y $BUS $ADDR 0x30 0x31 0x00 i2cset -y $BUS $ADDR 0x30 0x32 0x00 # 读回验证 VAL$(i2cget -y $BUS $ADDR 0x30) echo reg 0x3030 $VAL这个脚本里i2cset的第三个参数是寄存器地址高字节第四个是低字节第五个是数据。注意 IMX335 的寄存器地址是 16 位所以i2cset要分两次写地址。如果i2cget返回 0xFF 或者报错说明 I2C 通信有问题先查总线和上拉电阻。5.2 用 v4l2-ctl 抓一帧看 MIPI 是否有数据如果驱动已经注册成功但 VI 没数据可以用v4l2-ctl直接抓 sensor 的 MIPI 输出。海思的 VI 设备通常对应/dev/video0命令如下v4l2-ctl -d /dev/video0 --set-fmt-videowidth2592,height1944,pixelformatRG10 v4l2-ctl -d /dev/video0 --stream-mmap --stream-count1 --stream-toframe.raw如果frame.raw大小是 0 或者全是 0说明 MIPI 没有数据。这时候要回去查combo_dev_attr的 lane 配置和 clock 极性。如果frame.raw有数据但图像不对那就是 Bayer 顺序或者曝光增益的问题。5.3 一个快速定位时序问题的检查表下面这张表是我在调试 IMX335 HI3559 时常用的检查顺序按这个顺序排查能省很多时间检查项正常值异常表现修改位置I2C chip id0x33读不到或 0xFFsensor_i2c_infoPLL 寄存器0x30300x02MIPI 无时钟imx335_init_regslane 数0x30400x00VI sync 丢失combo_dev_attrdata typeRAW10花屏或黑屏mipi_attrBayer 顺序RGGB偏绿或偏紫isp_bayer_format行时间系数22200曝光偏亮或偏暗imx335_set_exposure这张表里的值是基于 24MHz 输入、4 lane、全分辨率的配置如果你的硬件不同对应值要调整。我一般会先把 I2C 和 PLL 确认再看 MIPI 配置最后调 ISP 参数。顺序反了容易在错误的方向上浪费时间。5.4 一个我踩过的坑寄存器序列里的延时IMX335 的初始化序列里有些寄存器之间需要延时比如退出 standby 之后要等 10ms 才能写其他寄存器。海思的sensor_write_register是阻塞的但不会自动加延时。如果你把序列一次性写完sensor 可能还没准备好后面的寄存器写入会失败。我的做法是在imx335_init里分段写每段之间加usleep_range(1000, 2000)。具体哪些寄存器之间需要延时Sony 的手册里没有明确写我是通过反复试验确定的。一般来说PLL 配置之后、MIPI 全局设置之后、退出 standby 之后各加一次延时基本能覆盖。提示如果你用的是海思 SDK 自带的 sensor 驱动模板里面通常已经有一些延时不要随便删掉。那些延时是前人踩坑踩出来的删了可能当时能跑但批量生产时会出现偶发失败。5.5 验证驱动稳定性的一个习惯驱动跑通之后我一般会做一个 24 小时的老化测试让 sensor 连续出图每隔一小时读一次 chip id 和几个关键寄存器看有没有漂移。同时用cat /proc/umap/isp看 ISP 的中断计数和错误计数。如果错误计数在涨说明 MIPI 时序有偶发问题可能是 clock 极性或者 lane 映射在边界条件下不稳定。这个习惯帮我抓到过好几次偶发问题比如某个批次的模组 PLL 锁定时间偏长初始化序列里的延时不够导致千分之几的失败率。这种问题在实验室里跑一天可能都遇不到但量产时会很致命。希望帮到你。本文还有配套的精品资源点击获取