msm8953平台Linux Camera移植实战:DTS配置与V4L2调试 1. 项目缘起与整体设计思路1.1 为什么选这个平台做Camera移植手里这块基于msm8953的板子最早是跑安卓的现在要把它改成跑Linux做视觉采集网关用。Camera这块是重头戏因为整个产品的核心功能就是图像采集、编码、推流。msm8953这颗SoC在当年是高通中端主力Camera子系统用的是CSIPHY CSID ISP VFE这一套管线Linux主线内核对它的支持其实不算完整很多驱动得自己动手补。我选这个平台的原因很直接一是硬件便宜好搞二是社区里关于msm8953的Camera资料虽然零散但关键节点都有人踩过坑三是它的Camera管线结构清晰搞懂一条通路之后其他通路照葫芦画瓢就行。适合谁看适合那些手里有msm8953板子、想跑Linux、又需要接MIPI Camera的嵌入式工程师。如果你连DTS都没写过建议先补一下设备树基础不然看下去会比较吃力。1.2 整体方案选型与架构拆解msm8953的Camera数据流大致是这样的Sensor出MIPI CSI-2信号进CSIPHY做物理层解析再进CSID做解码和格式转换然后送到ISP做图像处理最后通过VFE写入内存。Linux这边用的是V4L2框架media controller把各个子设备串起来用户空间用v4l2-ctl或者自己写应用来抓图。我选的是主线内核5.10因为5.10对msm8953的Clock、GDSC、Interconnect支持已经比较完整了Camera相关的驱动虽然不在主线里但社区有patch可以打。Sensor选的是OV5640原因很简单OV5640的驱动在主线内核里有现成的DTS配置也容易找参考而且它支持多种分辨率和格式调试起来灵活。整个方案的核心思路是先把Clock、Regulator、GPIO这些基础设施配通再配CSIPHY和CSID然后配ISP和VFE最后把Sensor挂上去。每一步都要用media-ctl和v4l2-ctl验证确保链路是通的。这个顺序不能乱因为后面的设备依赖前面的设备先就绪。注意msm8953的Camera子系统里CSIPHY和CSID是成对出现的配的时候要确认用的是哪一组PHY和哪个CSID别配错了。我一开始就是PHY0配到了CSID1上结果死活出不了图查了两天才发现是DTS里写错了。1.3 移植前需要准备的东西硬件方面你需要一块msm8953的开发板、一个MIPI Camera模组我用的是OV5640、一根FPC排线、一个串口调试工具。软件方面你需要一份能编译的Linux内核源码5.10、一个交叉编译工具链、一份msm8953的DTS文件、以及v4l-utils工具包。工具链我用的aarch64-linux-gnu-gcc版本是9.3.0。v4l-utils要编译到目标板上因为板子上没有包管理器只能自己交叉编译。编译v4l-utils的时候记得把libv4l也编进去不然v4l2-ctl有些功能用不了。提示编译内核之前先把DTS里关于Camera的节点全部注释掉确保系统能正常启动。等系统起来了再一个一个把Camera节点加回去这样出问题的时候容易定位是哪个节点导致的。2. 核心细节解析与DTS配置实操2.1 Clock与Regulator的配置要点msm8953的Camera子系统涉及多个Clockcamss_top_ahb_clk、camss_ahb_clk、camss_csi_clk、camss_csiphy_clk、camss_ispif_ahb_clk、camss_vfe_ahb_clk、camss_vfe_axi_clk等等。这些Clock在DTS里要一个一个配好缺一个都可能导致驱动probe失败。Regulator方面Camera模组通常需要几路供电AVDD模拟供电一般2.8V、DVDD数字核心供电一般1.2V、DOVDDIO供电一般1.8V。OV5640还需要一个VDDIO。这些Regulator在DTS里要用fixed-regulator或者pmic的regulator节点来配。我踩过的坑是AVDD和DOVDD的电压搞反了结果Sensor一直不出图I2C能读到ID但就是没数据。后来用万用表量了一下发现AVDD只有1.8VDOVDD反而是2.8V。改过来之后立马就出图了。所以配Regulator的时候一定要对着Sensor的datasheet确认电压值别凭感觉写。camss { status okay; vdda-supply pm8953_l2; vdd-csiphy-supply pm8953_l6; vdd-csid-supply pm8953_l6; vdd-isp-supply pm8953_l6; vdd-vfe-supply pm8953_l6; };上面这段是Camera子系统的主供电配置每个supply对应一路Regulator。pm8953_l2是1.2Vpm8953_l6是1.8V。具体用哪一路要看你的原理图。2.2 CSIPHY与CSID的DTS节点配置CSIPHY是MIPI物理层负责把差分信号解析成数字信号。msm8953有两个CSIPHY每个CSIPHY有四个lane。CSID是解码器负责把CSIPHY输出的数据做格式转换和错误校正。CSIPHY和CSID之间是固定映射的PHY0对应CSID0PHY1对应CSID1。DTS里配CSIPHY的时候要指定lane数、数据速率、以及和CSID的关联。lane数一般是1、2、4数据速率要看Sensor的输出速率。OV5640在1080p30fps下MIPI时钟大概是372MHz数据速率是744Mbps per laneDDR模式。csiphy0 { status okay; clocks camss_top_ahb_clk, camss_csiphy_clk; clock-names top_ahb, csiphy; assigned-clocks camss_csiphy_clk; assigned-clock-rates 266666667; vdd-csiphy-supply pm8953_l6; lanes 2; >csid0 { status okay; clocks camss_top_ahb_clk, camss_csid_clk; clock-names top_ahb, csid; assigned-clocks camss_csid_clk; assigned-clock-rates 400000000; phys csiphy0; phy-names csiphy; virtual-channel 0; };注意CSID的assigned-clock-rates一般是400MHz这个值也是从Clock树里算出来的。如果你改了CSIPHY的data-rateCSID的时钟也要相应调整不然会出现数据溢出或者欠载。2.3 ISP与VFE的配置细节ISP是图像信号处理器负责做去马赛克、白平衡、色彩校正这些处理。VFE是视频前端负责把ISP处理完的数据写入内存。msm8953的ISP和VFE是集成在一起的DTS里通常只配一个节点。ISP的配置主要是指定输入源从哪个CSID来、输出格式、以及中断和Clock。VFE的配置主要是指定DMA通道、输出分辨率、以及和ISP的关联。isp { status okay; clocks camss_top_ahb_clk, camss_ispif_ahb_clk, camss_vfe_ahb_clk, camss_vfe_axi_clk; clock-names top_ahb, ispif_ahb, vfe_ahb, vfe_axi; assigned-clocks camss_vfe_axi_clk; assigned-clock-rates 400000000; interrupts GIC_SPI 78 IRQ_TYPE_EDGE_RISING; vdd-isp-supply pm8953_l6; vdd-vfe-supply pm8953_l6; csid csid0; };上面这段是ISP的配置interrupts里的78是msm8953的ISP中断号这个值是从SoC手册里查到的。assigned-clock-rates是VFE AXI时钟一般是400MHz。VFE的DMA通道配置比较关键因为涉及到内存的读写。msm8953的VFE有四个DMA通道每个通道可以配不同的输出格式和分辨率。我一般用通道0输出YUV422通道1输出RAW10这样既能出图又能做调试。vfe { status okay; dma-channels 4; dma-channel-names vfe0, vfe1, vfe2, vfe3; output-formats yuv422, raw10, raw10, raw10; output-resolutions 1920 1080, 1920 1080, 1920 1080, 1920 1080; };2.4 Sensor节点的DTS配置Sensor节点是DTS里最复杂的部分因为要配I2C地址、MIPI lane数、Clock频率、Regulator、GPIO复位和电源使能、以及和CSIPHY的关联。OV5640的I2C地址是0x3c7位地址MIPI lane数是2Clock频率是24MHz。复位GPIO和电源使能GPIO要根据原理图来配。i2c1 { status okay; clock-frequency 400000; ov5640: camera3c { compatible ovti,ov5640; reg 0x3c; clocks camss_xo_clk; clock-names xclk; assigned-clocks camss_xo_clk; assigned-clock-rates 24000000; vdddo-supply pm8953_l6; vdda-supply pm8953_l2; vddd-supply pm8953_l2; reset-gpios tlmm 40 GPIO_ACTIVE_LOW; pwdn-gpios tlmm 41 GPIO_ACTIVE_HIGH; port { ov5640_ep: endpoint { remote-endpoint csiphy0_ep; >export ARCHarm64 export CROSS_COMPILE/opt/buildroot/output/host/bin/aarch64-linux-gnu- make defconfig make menuconfig在menuconfig里要确保以下选项被选中Device Drivers - Media support - V4L2 sub-device userspace APIDevice Drivers - Media support - Media controller APIDevice Drivers - Media support - Platform devices - Qualcomm CAMSSDevice Drivers - Media support - I2C encoders, decoders, sensors - OV5640然后编译内核和设备树make -j8 make dtbs编译完之后把arch/arm64/boot/Image和arch/arm64/boot/dts/qcom/msm8953-xxx.dtb拷到板子的boot分区。注意编译DTS的时候如果报错说某个节点找不到大概率是节点名写错了或者父节点没配好。我遇到过csiphy0节点找不到的情况后来发现是camss节点的status没设成okay导致子节点都被禁用了。3.2 系统启动与驱动加载验证板子启动之后先看dmesg里有没有Camera相关的报错。dmesg | grep -i camss dmesg | grep -i csiphy dmesg | grep -i ov5640如果驱动加载成功你应该能看到类似这样的输出camss: camss_probe: CAMSS probed successfully csiphy: csiphy_probe: CSIPHY0 probed successfully ov5640: ov5640_probe: OV5640 probed successfully如果看到probe failed就要根据报错信息去查。常见的错误有Clock没配好、Regulator没配好、GPIO没配好、I2C通信失败。我遇到过一次ov5640 probe failed报错是“failed to get xclk”。后来发现是camss_xo_clk这个Clock在DTS里没定义加上之后就好了。3.3 media-ctl链路配置与验证驱动加载成功之后用media-ctl查看media设备拓扑。media-ctl -d /dev/media0 -p你应该能看到类似这样的拓扑- entity 1: ov5640 1-003c (1 pad, 1 link) pad0: Source - msm_csiphy0:0 [ENABLED] - entity 2: msm_csiphy0 (1 pad, 1 link) pad0: Sink - ov5640 1-003c:0 [ENABLED] pad0: Source - msm_csid0:0 [ENABLED] - entity 3: msm_csid0 (1 pad, 1 link) pad0: Sink - msm_csiphy0:0 [ENABLED] pad0: Source - msm_isp:0 [ENABLED] - entity 4: msm_isp (1 pad, 1 link) pad0: Sink - msm_csid0:0 [ENABLED] pad0: Source - msm_vfe0:0 [ENABLED] - entity 5: msm_vfe0 (1 pad, 1 link) pad0: Sink - msm_isp:0 [ENABLED] pad0: Source - msm_vfe0_video:0 [ENABLED]如果链路是ENABLED的说明拓扑没问题。如果是IMMUTABLE或者DISABLED就要用media-ctl去配置。media-ctl -d /dev/media0 -l ov5640 1-003c:0-msm_csiphy0:0[1] media-ctl -d /dev/media0 -l msm_csiphy0:0-msm_csid0:0[1] media-ctl -d /dev/media0 -l msm_csid0:0-msm_isp:0[1] media-ctl -d /dev/media0 -l msm_isp:0-msm_vfe0:0[1] media-ctl -d /dev/media0 -l msm_vfe0:0-msm_vfe0_video:0[1]然后配置格式media-ctl -d /dev/media0 -V ov5640 1-003c:0[fmt:UYVY8_2X8/1920x1080] media-ctl -d /dev/media0 -V msm_csiphy0:0[fmt:UYVY8_2X8/1920x1080] media-ctl -d /dev/media0 -V msm_csid0:0[fmt:UYVY8_2X8/1920x1080] media-ctl -d /dev/media0 -V msm_isp:0[fmt:UYVY8_2X8/1920x1080] media-ctl -d /dev/media0 -V msm_vfe0:0[fmt:UYVY8_2X8/1920x1080]提示格式配置的时候每个子设备的格式要一致不然会出现格式不匹配的错误。我一开始只配了Sensor和VFE的格式中间的CSIPHY和CSID没配结果v4l2-ctl抓图的时候报“format mismatch”。3.4 v4l2-ctl出图验证格式配好之后就可以用v4l2-ctl抓图了。v4l2-ctl -d /dev/video0 --set-fmt-videowidth1920,height1080,pixelformatUYVY v4l2-ctl -d /dev/video0 --stream-mmap --stream-count1 --stream-toframe.raw如果一切正常frame.raw里应该有1920x1080x24147200字节的数据。用ffmpeg或者yuvplayer打开应该能看到图像。ffmpeg -f rawvideo -pix_fmt uyvy422 -s 1920x1080 -i frame.raw -f image2 frame.png如果frame.raw是空的或者大小不对就要检查链路和格式配置。常见的问题有链路没使能、格式不匹配、DMA通道没配好、Clock频率不对。我遇到过一次frame.raw只有一半大小的情况后来发现是VFE的DMA通道只配了一个而实际用了两个通道。把DMA通道数改成2之后就好了。4. 常见问题与排查技巧实录4.1 驱动probe失败排查表报错信息可能原因解决方法failed to get xclkClock没配检查DTS里clocks和clock-namesfailed to get regulatorRegulator没配检查DTS里supply和Regulator节点failed to get gpioGPIO没配检查DTS里gpio和pinctrli2c transfer failedI2C通信失败检查I2C地址、上拉电阻、电源timeout waiting for csidCSID没响应检查CSID Clock和中断dma allocation failedDMA内存不足检查CMA大小和DMA通道配置4.2 出图异常排查思路如果驱动probe成功但出图异常可以按以下顺序排查用media-ctl确认链路是ENABLED的用media-ctl确认每个子设备的格式是一致的用v4l2-ctl确认video设备的格式和media-ctl配的一致用示波器量MIPI信号确认有数据输出用dmesg看有没有CSID或VFE的报错用v4l2-ctl抓RAW图确认Sensor本身有数据我遇到过一次出图是绿屏的情况后来发现是ISP的白平衡没配好。在DTS里加上ISP的awb配置之后就好了。4.3 性能优化与稳定性调优出图之后还要做性能优化。msm8953的Camera子系统有几个性能瓶颈CSIPHY的data-rate、CSID的Clock、VFE的AXI Clock、以及DMA的内存带宽。如果帧率上不去可以先看CSIPHY的data-rate是不是够。1080p30fps需要744Mbps per lane如果data-rate设低了帧率就上不去。然后看CSID的Clock是不是够400MHz一般够用。最后看VFE的AXI Clock400MHz一般也够用。如果图像有撕裂或者丢帧可能是DMA内存带宽不够。可以试试增大CMA的大小或者把DMA通道的优先级调高。注意调Clock频率的时候不要超过SoC手册里规定的最大值不然会不稳定。我试过把CSID的Clock调到500MHz结果系统直接挂了。4.4 独家避坑经验分享第一个坑DTS里Camera节点的顺序很重要。camss节点要在csiphy和csid之前csiphy和csid要在isp和vfe之前isp和vfe要在sensor之前。顺序错了驱动probe的时候会找不到依赖。第二个坑Regulator的电压一定要对着datasheet确认。我见过有人把1.2V的DVDD配成了1.8V结果Sensor直接烧了。第三个坑MIPI lane的极性不能搞反。data-lanes是1 2还是2 1要看原理图上的走线。搞反了数据是乱的。第四个坑v4l2-ctl抓图的时候--stream-count不要设太大不然会占满内存。我一般设1或者10抓完就停。第五个坑media-ctl配置链路的时候要用双引号把entity名括起来不然会报“entity not found”。entity名里有空格和特殊字符不加引号会被shell解析错。5. 后续扩展与个人体会这套方案跑通之后可以往几个方向扩展。一是加第二个Cameramsm8953支持双Camera可以配CSIPHY1和CSID1。二是加ISP的3A算法msm8953的ISP支持自动曝光、自动白平衡、自动对焦可以在用户空间用v4l2-ctl去调。三是加编码和推流msm8953有硬件编码器可以用V4L2 M2M接口去调。我个人在实际操作中的体会是Camera移植这件事七分靠DTS三分靠调试。DTS配好了后面基本就是顺水推舟。DTS配不好后面怎么调都是白搭。所以建议大家在配DTS的时候一定要对着SoC手册和Sensor datasheet一个参数一个参数地确认不要凭感觉写。另外media-ctl和v4l2-ctl这两个工具一定要用熟。它们是你和Camera子系统对话的窗口链路通不通、格式对不对、数据有没有全靠它们来验证。我刚开始的时候不太会用media-ctl走了很多弯路。后来把media-ctl的man page从头到尾看了一遍才发现之前很多问题其实用一条命令就能定位。最后再分享一个小技巧如果你不确定某个Clock的频率该设多少可以先把所有Clock都设成SoC手册里的最大值然后逐步往下调直到系统稳定为止。这样虽然费点时间但能保证不会因为Clock设低了导致性能问题。