资讯详情

资讯详情

MTK Camera驱动流程详解:从Sensor上电到ISP出图

做MTK平台Camera驱动也有些年头了隔三差五会有做方案或者刚入驱动坑的朋友来问驱动流程到底怎么走从开机到预览中间到底发生了什么MTK的sensor驱动为什么这么写这类问题问的人多网上资料又散大部分是原厂文档的复制粘贴读起来费劲看完还是不知道从哪里下手。这篇文章就是把MTK平台Camera驱动的完整流程拎出来捋一遍从硬件链路到内核驱动从Sensor上电到ISP出图再配上我实际调试中踩过的一些坑。适合刚接触MTK Camera驱动的开发、FAE或者做项目移植想快速上手Sensor适配的同学。内容尽量说人话涉及具体数据结构的部分会说明它存在的意义而不是只丢个结构体出来。1. 先看清整条链路Camera驱动在整个系统里站在什么位置开始写代码之前先理解这层关系。很多人一上来就抓着Sensor驱动死磕搞了半天发现预览还是黑的问题根本不出在Sensor而是在更上层或者更下层。先把地图铺开才不至于迷路。1.1 硬件侧的几个关键物件Sensor、ISP、CSI接口摄像头这个外设和屏幕上那些外设不太一样它从数据产生到进内存中间要经过一串硬件单元。最前面是Lens加SensorSensor把光信号转成电信号再通过MIPI CSI接口把数据传到主控端的ISP。MTK平台的ISP和基带一般是集成在同一个SoC里的SoC内部有ISP硬件模块负责做RAW域处理输出YUV或者RGB的数据供上层使用。这里有个点要理解Sensor并不是只有一个“感光芯片”。常见的Sensor模组上还带了VCM音圈马达对焦用的、EEPROM存校准数据和Sensor配置、有时候还带OIS驱动。所以在驱动里你看到的“一颗Sensor”往往实际上是一个模组驱动代码里会同时处理I2C上多个从设备地址这都是硬件上真实存在的。在驱动流程分析里最核心的硬件角色有三个Sensor负责采集图像输出RAW或者YUV数据通过I2C做寄存器控制。MIPI CSI / Cphy / Dphy负责物理传输把Sensor输出的串行数据接收到SoC侧。ISP负责将RAW数据做坏点校正、降噪、色彩插值、Gamma等处理输出最终可显示的图像。1.2 软件侧的分层从APK到寄存器中间隔着谁MTK平台的Camera软件栈从上层往下大概是这么个结构应用层相机App→ Camera Framework含CameraService→ Camera Provider / HAL → V4L2框架 / MTK自有HAL → 内核驱动含Sensor驱动、ISP驱动、CCI接口驱动→ 硬件。其中和驱动开发者最相关的是HAL到内核这一层。MTK的Camera HAL会通过V4L2节点通常是/dev/video0或者media controller下的多个video节点和内核交互。Sensor驱动在内核里是以V4L2 subdev的形式挂载的HAL通过Media Controller接口找到对应的subdev然后调用它来配置Sensor。换句话讲你在MTK平台上做Sensor适配本质上是写一个符合V4L2 subdev规范的驱动然后把驱动注册到Media Controller拓扑里让上层HAL能通过标准接口控制它。理解了这个你对“为什么probe函数里要注册这么多回调”就不会觉得莫名其妙了。提示不同MTK平台的vendor代码可能封装程度有差异但大体都脱离不了V4L2 subdev这个基础模型。把标准框架吃透换平台只是换API壳的问题。2. 内核里的Sensor驱动注册、匹配、probe到底做了啥很多人第一次打开MTK的Sensor驱动源码首先困惑的是这个驱动怎么和我以前写的字符设备驱动长得不一样没有file_operations没有read/write全是一堆结构体和回调。这恰恰是因为它走的是V4L2 subdev框架而不是传统字符设备框架。2.1 驱动怎么被加载从device_node到probe函数看一下典型的Sensor驱动代码最显眼的通常是platform_driver结构体里面包含了probe和remove函数还有一个driver成员其中有个of_match_table。static const struct of_device_id sensor_of_match[] { { .compatible omnivision,ov5647-mtk }, { /* sentinel */ } }; static struct platform_driver this_sensor_driver { .probe sensor_probe, .remove sensor_remove, .driver { .name sensor_ov5647, .of_match_table sensor_of_match, }, }; module_platform_driver(this_sensor_driver);这个驱动会和设备树里的节点做匹配。设备树里通常会有一个描述这颗Sensor的节点比如i2c2 { sensor_ov5647: sensor36 { compatible omnivision,ov5647-mtk; reg 0x36; pwdn-gpios pio 15 0; reset-gpios pio 16 0; clocks clk26m; clock-frequency 24000000; ... }; };当设备树节点和驱动里的of_match_table匹配成功后内核就会调用probe函数。这里有个容易被忽略的点Sensor是挂在I2C总线上的但驱动本身是platform driver。为什么不是i2c_driver因为Sensor驱动除了要访问I2C寄存器之外还要控制GPIO上下电、配置时钟、处理中断这些资源都是平台相关的不是标准I2C协议能覆盖的。所以MTK的做法是一个platform driver负责整体控制内部通过I2C adapter来访问Sensor寄存器。probe函数的第一个任务是读取Sensor的ID验证I2C通信是否正常。大多数Sensor都有ID寄存器比如OV系列一般是寄存器0x300A和0x300B里面放着厂商ID。如果读出来的值和驱动里定义的不一致probe会直接报错返回因为这说明电路连接有问题或者驱动的I2C地址、寄存器配置值搞错了。读ID通过之后probe会把Sensor驱动的各项能力数据填充到v4l2_subdev结构里包括支持的分辨率列表、支持的格式、当前的曝光/增益控制方式等。这部分数据是给上层ISP和HAL做配置协商用的非常关键。2.2 另一个核心内容power sequenceprobe里通常不做真正的上电动作只是通过devm_gpio_request之类的接口把GPIO资源申请好。真正的上电时序是在stream on的时候才执行因为Sensor进入工作模式才会需要电源。上电时序这个东西看起来只是“拉高拉低几个GPIO”实际坑特别多。比如某颗Sensor要求先上AVDD再上DOVDD然后时钟稳定100ms之后才能拉高Reset脚如果顺序反了或者延时不够Sensor可能能读ID但输出图像异常或者干脆不稳定。MTK平台给每个Sensor驱动留了一个power_on/power_off回调里面就用来做这些事。我贴一个简化版本的样子static int sensor_power_on(struct v4l2_subdev *sd) { // 1. 使能I2C电源和时钟域 // 2. 拉高pwdn引脚让Sensor进入工作状态 // 3. 等待10ms // 4. 拉高reset引脚 // 5. 等待20ms // 6. 设置MCLK频率 return 0; }MTK的CCICamera Control Interface本质上就是I2C控制器但驱动里通常会用mtk_cci_i2c_read/write这类封装接口而不是直接用内核标准I2C接口。原因有两点一是CCI接口需要处理时钟频率和时序配置二是为了配合MTK平台内部的Camera硬件模块做统一的资源管理。不过你写Sensor驱动时基本不需要关心底层的收发实现只要调用框架提供的接口就行。2.3 探针之后还要注册什么subdev、pad、链接probe函数末尾一般会注册V4L2 subdev并初始化media entity的pad。这个非常容易忽略但恰恰是上层能“看见”Sensor的关键。static const struct v4l2_subdev_ops sensor_subdev_ops { .core sensor_core_ops, .video sensor_video_ops, .pad sensor_pad_ops, }; static const struct media_entity_ops sensor_media_ops { .link_setup sensor_link_setup, ... };注册完成后Sensor在Media Controller拓扑图里就有一个节点了。上层ISP驱动的subdev会和Sensor的sink pad建立link数据流向就变成Sensor pad0source→ ISP subdev pad0sink→ ... → video设备节点只要这个link建立成功HAL就能通过查询media拓扑图找到Sensor对应的subdev。这一步如果没做对HAL会报找不到sensor节点预览永远起不来。3. 从开机到屏幕预览一次完整的出图流程全解很多新手最想搞明白的就是按一下相机App屏幕亮起画面这背后驱动到底做了哪些事。这一节把整个流程按时间顺序拆开每个步骤对应到驱动代码层面是怎么体现的。3.1 开机阶段驱动加载与sensor识别系统开机后内核根据设备树枚举I2C设备匹配到Sensor驱动执行probe。probe里会通过CCI接口读取Sensor ID确认通信正常然后注册V4L2 subdev。此时Sensor还没有真正上电出图只是完成了“我自己在系统里登记了”这件事。上层HAL在启动相机服务的时候会遍历内核Media Controller拓扑找到所有subdev节点然后查询每个subdev的能力。具体来说就是调用VIDIOC_SUBDEV_ENUM_MBUS_CODE、VIDIOC_SUBDEV_S_FMT这类ioctl向Sensor查询支持的图像格式和分辨率。Sensor驱动里对应的回调会把定义好的分辨率列表和格式信息返回给HAL。这个阶段非常容易出问题。比如HAL枚举不到Sensor十有八九是设备树匹配失败或者probe报错提前返回了。我排查时习惯先看内核日志里有没有probe成功打印再看media-ctl -p能不能看到对应的entity节点。如果没有节点说明subdev根本没注册进来后面的事情都不用谈了。3.2 打开相机到出第一帧PreviewHAL与驱动的交互序列相机App点开预览HAL层会做一系列操作触发内核驱动的完整调用链。大体上是下面的序列HAL下发open命令打开对应的video节点创建session。HAL通过configure_streams下发流配置包含分辨率、帧率、像素格式等参数。这个配置会一路传到Sensor驱动。内核驱动把流配置翻译成Sensor需要的寄存器配置并通过CCI接口写进Sensor。HAL触发process_request把各个subdev的配置打包发送驱动开始真正的stream on操作。Sensor上电、输出MIPI信号经过CSI接口进入ISPISP处理完之后通过DMA把图像数据写入内存。驱动检测到帧完成中断frame sync event上报给HALHAL把buffer交还上层屏幕显示图像。从驱动开发者角度真正需要关心的重点是第2步到第5步。这里展开讲一下每一步对应的代码逻辑。configure_streams下来之后Sensor驱动的set_fmt回调会被调用上层会把目标分辨率和格式传进来。你要在回调里判断这个分辨率是否在驱动支持的列表里然后记录当前的stream配置。这里要注意Sensor驱动通常支持多个分辨率比如1600x120030fps、800x60060fps等等不同分辨率对应不同的曝光行数、帧长度寄存器、HTS/VTS设定这些都是sensor datasheet给出的你在适配驱动时要一条条配好。注册表配置是通过init_settings、resolution_settings、stream_on_settings、stream_off_settings这种分段的方式组织的。MTK的Sensor驱动模板里一般有这几个数组sensor_init_settings初始化寄存器组包括输出格式、MIPI lane数、曝光模式配置。sensor_preview_settings预览分辨率对应的寄存器组设置HTS、VTS、PLL分频系数等。sensor_capture_settings拍照分辨率对应的寄存器组。sensor_stream_on_settings开启MIPI输出的一小组寄存器。sensor_stream_off_settings关闭输出的寄存器组。实际的stream on流程驱动会先把init_settings写一遍确认sensor处于一个确定的状态然后写入对应分辨率的setting最后写stream on寄存器让Sensor开始吐出数据。这里有一个我们反复踩坑的点init_settings一般在probe或者stream on最开始全量写一遍但有些Sensor某些寄存器比如曝光寄存器是continuous update的如果你在init里写死了一个值后面又没更新就会导致预览亮度异常或者曝光不准。另一个重要的点sensor_stream_on_settings里的0x0100寄存器stream enable很多Sensor的数据手册定义的值为0x01表示开启0x00表示关闭。但具体是MSB还是LSB first不同厂商不一样。驱动里的I2C读写也要配套有的Sensor是16-bit register address 8-bit data有的是16-bit address 16-bit data。搞错任何一个寄存器写入就会错位表现为画面异常或者Sensor完全没有输出。MIPI链路参数也要在这一步配置好。lane数、data rate、clock lane的连续/非连续模式这些必须和Sensor端配置匹配。如果Sensor输出4 lane而CSI接收端只配了2 lane画面会花掉或者直接没信号。这类问题通常查起来费劲因为日志里不一定会报错往往是数据对不上。我一般先看内核里CSI的日志有没有lane数不一致的告警再看抓的RAW图是不是有规律的错位。ISP管线启动之后驱动会注册帧完成中断。MTK平台通常有一个sofstart of frame和eofend of frame中断驱动通过等待队列把帧同步信息传给上层HAL。这里要注意的是如果Sensor的帧率配置不对比如VTS太小导致帧率过高ISP端可能来不及处理产生丢帧表现出来就是预览卡顿。3.3 Sensor切换分辨率时会发生什么预览和拍照一般分辨率不一样。当用户按下拍照键HAL会重新下发一轮配置驱动需要切到capture setting。这个切换过程里面有个头疼的问题切分辨率时MIPI链路要重新建立ISP的buffer可能还挂着旧帧。驱动里的做法一般是这样先把stream off掉等Sensor输出停止再写新的resolution setting然后重新stream on。但这个过程中间时序要控制好如果stream off没等到Sensor彻底停下就切寄存器Sensor内部状态可能会错乱表现出来就是偶尔一张全黑或者切过去之后花屏。我在实际项目里处理过一个问题预览切拍照画面偶尔会闪一下绿屏。跟踪了很久发现是切分辨率时sensor的帧长度寄存器在stream off之后立刻被写入新值但Sensor内部还在输出旧配置的一帧导致这半帧数据是旧的。后来在切换时多加了一个缓冲延时等fifo清空后再写新配置问题就消失了。这种问题很难在文档里找到标准答案只能靠你对时序的敏感度和反复试验。4. 常见问题与排查技巧实录驱动开发里Debug占的时间远大于写代码。MTK Camera驱动的问题有时候很隐蔽日志看起来一切正常但就是不出图。这一节直接把最常见的问题整理成速查表再展开讲排查思路。4.1 Sensor ID读不出或者probe失败这是MTK Camera驱动开发最经典的新手问题。现象是dmesg里打印读取sensor id失败probe函数返回错误码/dev/videoX下看不到对应节点。首先查I2C通信。用示波器或者逻辑分析仪抓CCI的SDA和SCL波形确认有没有时钟、有没有ACK。如果波形正常但没有ACK大概率是I2C地址错了。很多Sensor支持7-bit地址和8-bit地址两种写法驱动里用的是7-bit左移一位的格式和datasheet上写的可能会差1个bit。我调试时习惯把datasheet上的地址换算一下再填进代码。其次是上电时序。Sensor ID读不出来有时候是AVDD/DOVDD还没稳定就发起了I2C读操作。这种情况时序图上看得不明显但实际操作中很常见。你可以在probe里读ID之前加一个几十ms的延时试试如果能读出来了说明就是上电时序不够。还有一类问题是reset脚状态不对。Sensor在reset拉低状态下I2C是没响应的如果你驱动的GPIO默认电平把sensor压制在复位状态ID自然读不出来。检查方式就是量一下reset脚和pwdn脚的实际电压跟datasheet要求的工作条件对比一下。4.2 预览黑屏或花屏的排查思路黑屏和花屏的问题定位路径不一样。黑屏一般是数据没进来花屏是数据进来了但参数不对。黑屏时先查MCLK有没有输出很多Sensor没有MCLK就不会启动内部时钟MIPI输出直接没有。用示波器点一下Sensor的MCLK引脚就能确认。其次是检查stream on之后Sensor有没有真的进入输出状态这个要看Sensor的寄存器状态可以通过读取0x0100之类的寄存器确认。花屏的话优先查lane数和data rate是否匹配。我把MIPI比作水管Sensor端按某个流速往ISP端灌水ISP端接收速度必须匹配快了数据溢出慢了又不够。常见的花屏纹理是左半边正常右半边错位或者有规律的颜色偏移基本都是lane数或data rate配置不对。另外一个隐蔽问题是HS/VS同步信号反了。某些Sensor可以用寄存器切换同步信号的极性如果驱动配错ISP解析出来的行场信息会乱花屏表现非常诡异有时候是斜纹、有时候是整屏随机噪声。这个配置在Sensor驱动和ISP两边的init setting里都要检查。4.3 调试工具和日志定位方法工具方面我实际项目中用到最多的是这几样dmesg内核日志看驱动加载和probe结果。/proc/device-tree/查看设备树解析结果确认节点有没有被正确识别。media-ctl查看media拓扑确认subdev和link状态。串口抓内核打印比adb更早能看到驱动挂载状态。MTK自带的Camera debug tool比如vendor提供的一些dump脚本用来查看当前是哪个sensor、哪条管线在跑。日志抓取上面我习惯在驱动关键路径上加上pr_info打印比如probe入口、stream on/off、分辨率切换、中断上报。虽然log多了会有点吵但排查问题时这些信息是保命的。调试完了再改成pr_debug或者直接删掉。注意调试Sensor驱动的时候不要只盯着内核日志。HAL层也会有sensor相关的日志输出两层配合着看才能定位到问题到底出在哪一侧。很多“内核驱动没问题”的结论其实是HAL那侧已经报错了。cat /sys/devices/platform/...这种sysfs节点也可以用来手动触发一些测试。比如有些平台支持手动写寄存器值来验证Sensor行为这种手段在排查问题时效率很高能帮你区分是驱动代码逻辑问题还是Sensor本身的状态异常。4.4 分辨率和帧率的匹配问题预览60fps但拍照只有10fps这类问题看着像是sensor性能不行其实很多时候是配置问题。Sensor的实际输出帧率取决于寄存器里VTS垂直消隐和PLL配置。如果VTS设得太大帧率就会掉下去反之帧率升高但可能超过sensor内部ADC的极限导致图像质量下降。计算帧率的方法很简单帧率 24MHzMCLK/HTS × VTS × 系数具体公式要看你用的Sensor的PLL结构。很多datasheet会直接给出推荐的HTS和VTS值偷懒一点直接用推荐值就行但如果你想调高帧率就要自己算了。这类调试需要一定的耐心但调好了之后效果很直接。还有一点Sensor的输出分辨率和ISP端接受的输入分辨率必须一致包括对齐方式。有些sensor输出的RAW宽度不是16字节对齐的而ISP硬件要求输入buffer按某种对齐方式分配没对齐就会导致ISP状态异常。这类问题表现上像是数据有问题实际上是你配置的尺寸和实际输出尺寸不匹配截图量一下像素数就能发现。5. 从零适配一颗Sensor的实操小结最后聊一下实际做新项目时从拿到一颗新Sensor到成功出预览图的步骤。这个流程我走过很多遍每一步都可以作为checklist用。5.1 拿到Sensor之后要先读哪些文档先看datasheet里的这几个部分I2C地址表、上电时序图、MIPI输出参数lane数、数据率、寄存器初始化表。这四项分别对应驱动代码里的I2C地址配置、power_on函数、stream_on配置、init_settings数组。数据手册这部分没吃透后面一定出问题。然后是EEPROM里的识别信息。很多Sensor模组的EEPROM里存了模组厂商、镜头ID、校准数据。驱动里有时需要读取EEPROM来判断是哪个模组版本不同版本可能需要不同的驱动参数。在量产项目中这颗EEPROM往往是区分采购批次的重要依据。5.2 代码层面按什么顺序改基于MTK现有Sensor驱动的模板复制一份出来然后依次改这些sensor_id和I2C地址。上电时序函数按照datasheet设置GPIO和延时。MIPI lane数和data rate。init_settings寄存器表。preview/capture对应的分辨率寄存器表。stream on/off寄存器。v4l2_subdev里上报的分辨率列表和格式信息。这个顺序不要跳。I2C地址不对后面全白搭。上电时序不对ID都读不出来。MIPI参数不对出图也是花屏。每一步都验证通过之后再往下走效率最高。5.3 一次成功上电后的验证步骤Sensor ID读出来之后先看驱动探测成功日志然后用media-ctl确认subdev节点存在再验证分辨率枚举是否正确。前面的验证都通过之后最后才是真正出图测试。如果出图有问题按上一节的排查思路从MCLK开始逐步往后查。在我个人的经验里适配一颗新Sensor最顺利的情况也要半天碰到奇怪的电源问题、时钟问题或者寄存器兼容问题耗上一两天也正常。不要指望一次编译就能直接出图Camera驱动调试是硬件、软件、时序三层互相验证的过程。保持条理清晰每一步都有依据问题就只是时间问题。最后分享一个习惯每适配一颗Sensor我都会把调试过程中遇到的关键问题记录下来特别是那些datasheet没写清楚的时序要求、寄存器雷区以及不同Sensor厂家之间的兼容性差异。等积累了几颗Sensor之后你会发现这些笔记的价值远超任何官方文档。下一次遇到差不多的Sensor踩坑概率会小很多。
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →