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 xclk | Clock没配 | 检查DTS里clocks和clock-names |
| failed to get regulator | Regulator没配 | 检查DTS里supply和Regulator节点 |
| failed to get gpio | GPIO没配 | 检查DTS里gpio和pinctrl |
| i2c transfer failed | I2C通信失败 | 检查I2C地址、上拉电阻、电源 |
| timeout waiting for csid | CSID没响应 | 检查CSID Clock和中断 |
| dma allocation failed | DMA内存不足 | 检查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是不是够。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设低了导致性能问题。