ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

msm8953平台Linux Camera移植实战:DTS配置与V4L2调试

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子系统涉及多个Clock:camss_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)、DOVDD(IO供电,一般1.8V)。OV5640还需要一个VDDIO。这些Regulator在DTS里要用fixed-regulator或者pmic的regulator节点来配。

我踩过的坑是:AVDD和DOVDD的电压搞反了,结果Sensor一直不出图,I2C能读到ID,但就是没数据。后来用万用表量了一下,发现AVDD只有1.8V,DOVDD反而是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.2V,pm8953_l6是1.8V。具体用哪一路,要看你的原理图。

2.2 CSIPHY与CSID的DTS节点配置

CSIPHY是MIPI物理层,负责把差分信号解析成数字信号。msm8953有两个CSIPHY,每个CSIPHY有四个lane。CSID是解码器,负责把CSIPHY输出的数据做格式转换和错误校正。CSIPHY和CSID之间是固定映射的,PHY0对应CSID0,PHY1对应CSID1。

DTS里配CSIPHY的时候,要指定lane数、数据速率、以及和CSID的关联。lane数一般是1、2、4,数据速率要看Sensor的输出速率。OV5640在1080p@30fps下,MIPI时钟大概是372MHz,数据速率是744Mbps per lane(DDR模式)。

&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-rate,CSID的时钟也要相应调整,不然会出现数据溢出或者欠载。

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地址是0x3c(7位地址),MIPI lane数是2,Clock频率是24MHz。复位GPIO和电源使能GPIO要根据原理图来配。

&i2c1 { status = "okay"; clock-frequency = <400000>; ov5640: camera@3c { 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 ARCH=arm64 export CROSS_COMPILE=/opt/buildroot/output/host/bin/aarch64-linux-gnu- make defconfig make menuconfig

在menuconfig里,要确保以下选项被选中:

  • Device Drivers -> Media support -> V4L2 sub-device userspace API
  • Device Drivers -> Media support -> Media controller API
  • Device Drivers -> Media support -> Platform devices -> Qualcomm CAMSS
  • Device 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-video=width=1920,height=1080,pixelformat=UYVY v4l2-ctl -d /dev/video0 --stream-mmap --stream-count=1 --stream-to=frame.raw

如果一切正常,frame.raw里应该有1920x1080x2=4147200字节的数据。用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-names
failed to get regulatorRegulator没配检查DTS里supply和Regulator节点
failed to get gpioGPIO没配检查DTS里gpio和pinctrl
i2c transfer failedI2C通信失败检查I2C地址、上拉电阻、电源
timeout waiting for csidCSID没响应检查CSID Clock和中断
dma allocation failedDMA内存不足检查CMA大小和DMA通道配置

4.2 出图异常排查思路

如果驱动probe成功,但出图异常,可以按以下顺序排查:

  1. 用media-ctl确认链路是ENABLED的
  2. 用media-ctl确认每个子设备的格式是一致的
  3. 用v4l2-ctl确认video设备的格式和media-ctl配的一致
  4. 用示波器量MIPI信号,确认有数据输出
  5. 用dmesg看有没有CSID或VFE的报错
  6. 用v4l2-ctl抓RAW图,确认Sensor本身有数据

我遇到过一次出图是绿屏的情况,后来发现是ISP的白平衡没配好。在DTS里加上ISP的awb配置之后就好了。

4.3 性能优化与稳定性调优

出图之后,还要做性能优化。msm8953的Camera子系统有几个性能瓶颈:CSIPHY的data-rate、CSID的Clock、VFE的AXI Clock、以及DMA的内存带宽。

如果帧率上不去,可以先看CSIPHY的data-rate是不是够。1080p@30fps需要744Mbps per lane,如果data-rate设低了,帧率就上不去。然后看CSID的Clock是不是够,400MHz一般够用。最后看VFE的AXI Clock,400MHz一般也够用。

如果图像有撕裂或者丢帧,可能是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. 后续扩展与个人体会

这套方案跑通之后,可以往几个方向扩展。一是加第二个Camera,msm8953支持双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设低了导致性能问题。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进