ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

嵌入式Linux摄像头驱动实战:I2C Sensor注册V4L2 Subdev全流程解析

嵌入式Linux摄像头驱动实战:I2C Sensor注册V4L2 Subdev全流程解析 前一阵调试一条摄像头通路Sensor端用的是格科微的GC2053挂在I2C3总线上通过MIPI CSI-2给到主控。整条链路从dmesg到出图折腾了两三天最后卡在了一个非常基础的问题上Sensor的v4l2_subdev没有按预期注册进V4L2框架导致上层media拓扑始终缺一个节点。标题里这几个词单拎出来都不难V4L2、I2C、Sensor、v4l2_subdev但串起来做还是会踩坑。这篇就是从驱动开发者的视角把“用I2C摄像头Sensor注册一个v4l2_subdev”这件事完整展开先讲明白设计思路再给一套能直接抄的作业最后把调试方法和常见问题一并交代清楚。这篇实战笔记适合正在做嵌入式Linux camera驱动的朋友也适合想搞懂V4L2 subdev机制的内核初学者。我这里以Linux 5.4.52内核为例这套流程在更高版本的6.x上也差不太多个别API有差异后面我会单独说。1. v4l2_subdev到底解决什么问题1.1 从整条摄像头链路看subdev的位置摄像头数据链路从Sensor开始经过MIPI CSI接收控制器、ISP、DMA引擎最后才到内存里的Buffer。在V4L2框架之前很多厂商驱动把这一整条链路都塞进一个video_device里应用层打开/dev/video0内部直接操作Sensor寄存器一旦换Sensor或换ISP整个驱动重写维护成本极高。V4L2把这条链路拆成了多个组件每个组件都是一个entity其中挂在I2C总线上的摄像头Sensor就是通过v4l2_subdev这个结构体向V4L2框架注册的。你可以把v4l2_subdev理解为“V4L2框架里的一个标准从设备接口”它包装了Sensor的电源控制、时钟管理、寄存器读写、格式协商、流开关等操作统一暴露给上层调用。我经常跟同事打比方v4l2_subdev相当于一个“驱动里面的设备驱动”它向上对接V4L2核心向下对接具体的I2C Sensor硬件。系统里的CSI控制器、ISP驱动都通过v4l2_subdev_call这个宏来调用Sensor的回调而不用担心这个Sensor是OV5640还是GC2053。1.2 为什么不能直接把Sensor做成/dev/videoX有人可能问Sensor也有输出数据为什么不能直接注册成一个video_device让应用层open之后直接读图像关键在于V4L2的应用层接口面对的是“视频流”而不是“视频流里的某一级硬件”。video_device对应的是DMA引擎最终把图像数据搬运到内存而Sensor本身只是链路里负责感光和格式化的一级。应用层设置分辨率、投递Buffer都是对着video_device做的中间隔着ISP和CSISensor只要根据上层要求的格式把数据送出来即可。如果把Sensor直接暴露为video_device应用层就得绕过ISP、绕过CSI控制器这显然不合理。所以在V4L2的框架里Sensor被抽象成subdev挂在链路的源头通过对应的回调函数参与整条pipeline的协商与启动。这也是v4l2_subdev存在的根本原因。2. 驱动骨架i2c_driver与v4l2_subdev的绑定方式2.1 i2c_driver的probe里要做什么Sensor通常是I2C设备所以驱动的主体一定是一个i2c_driver。probe函数相当于驱动的入口在这里完成硬件资源获取、Sensor初始化、v4l2_subdev注册三件事。static const struct i2c_device_id gc2053_id[] { { gc2053, 0 }, {} }; static struct i2c_driver gc2053_i2c_driver { .driver { .name gc2053, .of_match_table gc2053_of_match, }, .probe gc2053_probe, .id_table gc2053_id, }; module_i2c_driver(gc2053_i2c_driver);probe里第一步是分配并初始化驱动私有数据结构。Sensor驱动一般会维护一个结构体存放i2c_client指针、v4l2_subdev、电源/时钟资源、当前分辨率、寄存器配置数组等struct gc2053 { struct v4l2_subdev sd; struct media_pad pad; struct i2c_client *client; struct gpio_desc *pwdn_gpio; struct gpio_desc *reset_gpio; struct clk *mclk; struct regulator *avdd; struct regulator *dovdd; struct regulator *dvdd; struct mutex lock; struct v4l2_mbus_framefmt fmt; bool streaming; };这里有一个很容易忽略的点名字就叫gc2053但驱动内部结构体名建议跟模块名区分开否则光看名字容易和内核里的某个同名符号搞混。我在代码里习惯用芯片型号大写命名结构体比如struct gc2053而变量名用gc2053。2.2 ops回调你的Sensor对外暴露哪些能力v4l2_subdev的核心是ops它决定了这个subdev能做什么。在Sensor驱动里最常用的是v4l2_subdev_core_ops、v4l2_subdev_video_ops、v4l2_subdev_pad_ops三组。static const struct v4l2_subdev_video_ops gc2053_video_ops { .s_stream gc2053_s_stream, .g_frame_interval gc2053_g_frame_interval, }; static const struct v4l2_subdev_pad_ops gc2053_pad_ops { .enum_mbus_code gc2053_enum_mbus_code, .get_fmt gc2053_get_fmt, .set_fmt gc2053_set_fmt, }; static const struct v4l2_subdev_ops gc2053_ops { .core gc2053_core_ops, .video gc2053_video_ops, .pad gc2053_pad_ops, };s_stream是Sensor驱动里最重要的回调应用层启动视频流时会触发它。enable时要做完整的Sensor上电和初始化序列输出disable时把Sensor断电或置为standby上电顺序和时序一定要按datasheet来。2.3 核心数据结构像搭积木一样把Subdev拼起来把i2c_client和v4l2_subdev关联起来最直接的方式是调用v4l2_i2c_subdev_initv4l2_i2c_subdev_init(gc2053-sd, client, gc2053_ops);这个函数会做几件事把sd-name设置成client的名字把sd-dev指向client-dev把client-adap等I2C信息保存进sd并把ops绑定到sd上。之后可以通过sd-dev和client-dev互转调试时打印设备树路径很方便。还要注意断开了i2c_driver和subdev的关系后注销时也要成对出现通常在remove函数里调用v4l2_async_unregister_subdev和media_entity_cleanup。3. 手把手实操从设备树到注册回调3.1 设备树节点硬件资源的描述语言驱动要正常工作第一件事是把硬件资源描述清楚。设备树里Sensor节点挂在哪条I2C总线上就在哪个i2c节点下面compatible要和驱动里的of_match_table匹配reg是I2C设备地址。i2c3 { status okay; clock-frequency 400000; gc2053: gc205310 { compatible galaxycore,gc2053; reg 0x10; pinctrl-names default; pinctrl-0 gc2053_pins; reset-gpio pio 24 GPIO_ACTIVE_LOW; pwdn-gpio pio 25 GPIO_ACTIVE_HIGH; clocks clk_mclk; clock-frequency 24000000; avdd-supply reg_cam_avdd; dovdd-supply reg_cam_dovdd; dvdd-supply reg_cam_dvdd; }; };这个节点基本是一个Sensor设备树的完整模板。买到的模组原理图不同GPIO接法可能不一样但套路是一致的。注意reg 0x10是8bit I2C地址也就是7bit地址左移一位后的值。很多新人会把datasheet里的SCCB 7bit地址直接填进来导致I2C总是NACK。3.2 probe探测上电、读ID、初始化Subdev设备树资源获取完成后probe剩下的步骤就固定了gpiod_get拿GPIOclk_get拿MCLKregulator_get拿三路供电然后上电读ID。static int gc2053_probe(struct i2c_client *client) { struct gc2053 *gc2053; struct v4l2_subdev *sd; int ret; gc2053 devm_kzalloc(client-dev, sizeof(*gc2053), GFP_KERNEL); if (!gc2053) return -ENOMEM; gc2053-client client; mutex_init(gc2053-lock); gc2053-pwdn_gpio devm_gpiod_get_optional(client-dev, pwdn, GPIOD_OUT_HIGH); if (IS_ERR(gc2053-pwdn_gpio)) return PTR_ERR(gc2053-pwdn_gpio); gc2053-reset_gpio devm_gpiod_get_optional(client-dev, reset, GPIOD_OUT_HIGH); if (IS_ERR(gc2053-reset_gpio)) return PTR_ERR(gc2053-reset_gpio); gc2053-mclk devm_clk_get(client-dev, NULL); if (IS_ERR(gc2053-mclk)) return PTR_ERR(gc2053-mclk); gc2053-avdd devm_regulator_get(client-dev, avdd); if (IS_ERR(gc2053-avdd)) return PTR_ERR(gc2053-avdd); /* dovdd、dvdd同理 */ ret gc2053_power_on(gc2053); if (ret) return ret; ret gc2053_check_chip_id(gc2053); if (ret) goto err_power_off; sd gc2053-sd; v4l2_i2c_subdev_init(sd, client, gc2053_ops); sd-flags | V4L2_SUBDEV_FL_HAS_DEVNODE; gc2053-pad.flags MEDIA_PAD_FL_SOURCE; sd-entity.function MEDIA_ENT_F_CAM_SENSOR; ret media_entity_pads_init(sd-entity, 1, gc2053-pad); if (ret) goto err_power_off; ret v4l2_async_register_subdev(sd); if (ret) goto err_cleanup_media; return 0; err_cleanup_media: media_entity_cleanup(sd-entity); err_power_off: gc2053_power_off(gc2053); return ret; }gc2053_power_on的逻辑非常关键GPIO和regulator的顺序、延时都必须参考硬件设计。我这里只给一个常见实现static int gc2053_power_on(struct gc2053 *gc2053) { int ret; ret regulator_enable(gc2053-avdd); if (ret) return ret; ret regulator_enable(gc2053-dovdd); if (ret) goto disable_avdd; ret regulator_enable(gc2053-dvdd); if (ret) goto disable_dovdd; usleep_range(1000, 2000); ret clk_set_rate(gc2053-mclk, 24000000); if (ret) goto disable_dvdd; ret clk_prepare_enable(gc2053-mclk); if (ret) goto disable_dvdd; usleep_range(1000, 2000); gpiod_set_value_cansleep(gc2053-pwdn_gpio, 1); gpiod_set_value_cansleep(gc2053-reset_gpio, 1); usleep_range(10000, 20000); return 0; disable_dvdd: regulator_disable(gc2053-dvdd); disable_dovdd: regulator_disable(gc2053-dovdd); disable_avdd: regulator_disable(gc2053-avdd); return ret; }注意reset_gpio是GPIO_ACTIVE_LOW设备树里已经声明了active-low所以gpiod_set_value(1)在实际引脚上就是拉低。初学gpiod接口时经常搞反方向最后用万用表一量才反应过来这里特别提醒一下。读chip_id是本驱动的“验证性”步骤只有ID读对了才说明I2C通信正常Sensor已经在工作static int gc2053_check_chip_id(struct gc2053 *gc2053) { u32 id; int ret; ret gc2053_read_reg(gc2053, GC2053_REG_CHIP_ID_H, id); if (ret) return ret; if (id ! GC2053_CHIP_ID) return -ENODEV; return 0; }我建议所有sensor驱动都保留这一步否则设备树匹配上了但硬件没接好驱动也会一股脑往下注册后面调试photo流程时问题会被掩盖很久。3.3 注册进V4L2框架async_subdev的妙处probe最后一步是v4l2_async_register_subdev它会把subdev挂到v4l2_async_notifier上。这里有个很重要的设计逻辑如今大部分camera方案里sensor驱动和csi/isp驱动往往不在同一个模块甚至可能是完全不同的人写的大家在各自的驱动里异步探测。如果Sensor先probe成功而CSI还没准备好直接v4l2_device_register_subdev可能会失败。v4l2_async_register_subdev的好处就是先注册的subdev会等待对应的notifier完成绑定。当CSI驱动的async_notifier收到这个subdev后会在complete回调里创建media link把Sensor entity和CSI entity连接起来。我贴一个典型CSI侧notifier回调的例子static int csi_async_notifier_complete(struct v4l2_async_notifier *notifier) { struct csi_device *csi container_of(notifier, struct csi_device, notifier); struct v4l2_subdev *subdev; int ret; ret v4l2_device_register_subdev_nodes(csi-v4l2_dev); if (ret) return ret; list_for_each_entry(subdev, notifier-done, async_list) { ret media_create_pad_link(subdev-entity, 0, csi-sd.entity, 0, MEDIA_LNK_FL_ENABLED); if (ret) return ret; } return media_entity_create_pad_link(csi-sd.entity, 0, isp-entity, 0, MEDIA_LNK_FL_ENABLED); }新手经常找不到link到底在哪建就以为驱动注册完就算完了。实际上sensor驱动里只把subdev报上去link是在csi或isp的notifier complete里通过media_create_pad_link建立的。这一点理清之后media拓扑不完整的问题就很好排查了。4. 调试三板斧如何确认subdev真的注册成功4.1 内核日志与I2C探测驱动加载阶段第一步看dmesg有没有probe相关日志。我习惯在probe成功结尾打印一句dev_info(client-dev, gc2053 detected, chip_id0x%04x\n, id);如果内核日志里压根没出现这行先确认device tree节点是否被正确匹配dmesg | grep gc2053也可以直接去/sys/bus/i2c/devices下面看有没有生成设备目录。比如节点地址0x10就会看到3-0010这样的目录ls /sys/bus/i2c/devices/如果设备目录存在但probe没有执行大概率是compatible对不上如果目录都不存在就要回I2C总线上去查看i2cdetect能不能扫到地址i2cdetect -y 3扫描结果里如果0x10位置显示UU或10说明I2C设备在线。UU代表这个地址已经有驱动binding了10代表设备在线但没有驱动。4.2 media-ctl与v4l2-ctl的配合subdev注册成功后用media-ctl看整个媒体拓扑是最直观的media-ctl -d /dev/media0 -p正常情况下能看到sensor entity、csi entity以及它们之间的link。如果sensor实体存在但link缺失99%是CSI侧notifier的complete回调没执行或link创建失败去查csi驱动的notifier注册流程。v4l2-ctl可以列出所有subdev节点v4l2-ctl --list-subdevs我一般把media-ctl和v4l2-ctl配合起来用。先看拓扑确认数据通路再配格式确认格式协商最后采流确认数据真正流动起来。这三个阶段各自独立能精确缩小问题范围。4.3 采流不通时先查视频链路拓扑采流前需要先把media link配置好否则应用层会一直报“no entity found”这类错误media-ctl -v -l gc2053 3-0010:0-csi2:0[1] media-ctl -v -V gc2053 3-0010:0[fmt:SBGGR10_1X10/1920x1080]这里的format要跟sensor实际输出格式严格一致。GC2053输出raw10 Bayer所以mbus_code是MEDIA_BUS_FMT_SBGGR10_1X10对应内核里的定义。如果sensor输出raw8而你配了raw10后面ISP解出来的数据全是乱的。配置完链路就可以采流验证v4l2-ctl --set-fmt-videowidth1920,height1080,pixelformatNV12 --stream-mmap --stream-count1如果stream count为0或一直挂在等待状态把dmesg打开重点看s_stream有没有被调用以及csi中断有没有触发。5. 高频问题与排查速查表5.1 I2C读写失败的几个典型原因I2C读写失败是Sensor驱动开发里遇到最多的问题现象就是读chip_id总是返回错误或者i2c_transfer返回-6 (ENXIO)。常见原因有三类。第一I2C地址不对。7bit地址和8bit地址搞混是祖传问题。很多Sensor datasheet写的是7bit地址比如0x30而I2C子系统传输时用的是8bit地址需要左移一位得到0x60。设备树里填0x60driver里读chip_id的client-addr才会对得上。第二上电时序不够。Sensor对上电时序非常敏感我实测过某些模组复位拉低保持时间少于10ms就会导致I2C不回ACK。建议在power_on里头加上足够的usleep_range宁可多等也不能急。第三I2C控制器速率太高。Sensor通常支持400kHz到1MHz但有些模组的I2C上拉电阻选得不对速率稍高就出错。先降到100kHz试能通再慢慢提。5.2 probe一直重试或返回-517返回-EPROBE_DEFER说明驱动依赖的资源还没准备好常见的是regulator、clock、interrupt由其它驱动提供而那个驱动还没probe。这个不是错误内核会在依赖加载后重新调用probe。可以通过debugfs查看谁还在deferred列表里cat /sys/kernel/debug/devices_deferred输出里会明确显示设备名字和defer的原因。如果长期卡在deferred状态通常是设备树里引用的regulator或clk节点status不对或者对应驱动没有加载。5.3 s_stream被调用后依然没有图像这个问题最烧时间。代码逻辑上s_stream确实被调了但采集端就是没数据。我的排查顺序是这样。先确认MCLK频率用示波器或逻辑分析仪量MCLK引脚24MHz正负误差一般要在几kHz以内。很多平台MCLK默认是其它频率比如26MHz或19.2MHz务必用clk_set_rate显式设置。再确认reset和pwdn的最终电平状态。SDK有时会默认把pwdn拉高导致Sensor一直在powerdown状态。两个GPIO在s_stream前必须处于正确电平这里建议用gpio调试接口确认cat /sys/kernel/debug/gpio然后查Sensor寄存器确认stream on序列有没有正确下发。到这一步可以直接读PLL状态寄存器看看Sensor内部有没有lock。最后查MIPI CSI侧lane数、data type、时钟参数是否跟Sensor输出匹配。这几项只要有一项不对CSI控制器就收不到有效数据。5.4 不同内核版本的API差异Linux 5.4和Linux 6.x的subdev API有一处明显变化是get_fmt/set_fmt回调多了一个参数/* 5.4 */ int (*get_fmt)(struct v4l2_subdev *sd, struct v4l2_subdev_format *fmt); /* 6.x */ int (*get_fmt)(struct v4l2_subdev *sd, struct v4l2_subdev_state *state, struct v4l2_subdev_format *fmt);这意味着不同内核版本间搬运sensor驱动时不能只改头文件就完事ops回调签名也需要适配。另一个要注意的点是旧版的v4l2_video_ops里有g_mbus_config等回调新内核已经移到了v4l2_subdev_pad_ops改动时容易漏。还有一个容易踩的坑是compilation是否使用v4l2_subdev_state的lock。新内核里如果用了v4l2_subdev_lock/unlock一定要保证所有回调路径成对调用否则会出现莫名其妙的死锁。我把这些高频问题整理成一张速查表方便现场调试时快速定位现象可能原因操作建议i2cdetect扫不到地址I2C地址错误/电源未上/复位未释放核对8bit地址量上电时序复位拉低延时后再拉高probe执行了但读ID失败MCLK没起/GPIO电平不对/I2C速率过高用示波器量MCLK检查pwdn/reset电平降I2C速度到100kHzprobe返回-517依赖regulator/clk/gpio未就绪查看devices_deferred确认设备树依赖节点正常media-ctl -p看不到sensor entityasync subdev未注册或CSI notifier未匹配查sensor驱动注册日志查csi notifier complete回调有sensor entity但无linklink创建失败或notifier complete没执行检查media_create_pad_link参数确认entity pad索引s_stream被调用但无图像MCLK不对/CSI lane配置错/stream on序列异常量时钟、查CSI参数、读sensor PLL状态寄存器最后再分享一个我这次的经历。GC2053一直读不到IDi2c_transfer返回-6I2C地址对照过、电压对过、MCLK也对过最后发现是reset时序。我把reset_gpio拉高active-low对应硬件的低电平后没有给足够延时就去发I2C读IDSensor芯片实际上还没完成内部初始化。V4L2 subdev驱动写起来确实是套路化的事情一遍搭骨架很容易但真正考验人的是每个硬件细节。希望这篇笔记能让你少走点弯路有疑问可以在评论区一起讨论。
返回列表