GV7704四路SDI采集卡驱动链路配置与稳定性排查 简介gv7704驱动.rar 面向基于 Hisi3531A 芯片平台进行 SDI 视频采集与设备控制的嵌入式开发者重点解决 GV7704 硬件在 Hisi3531A 平台下的输入、读写与状态查询适配问题。压缩包共 10 个文件约 90KB以 7 个 c 源文件、2 个 h 头文件和 1 个 Makefile 为主涵盖驱动读写、SPI/GPIO 接口、字符串处理及测试入口便于理解模块划分与编译组织。已有 696 人学习适合需要参考 Hisi3531A 平台驱动移植、SDI 输入调试和查询控制实现的工程师。代码可用于梳理设备初始化、视频流参数读取、寄存器访问与测试流程也能作为排查编译适配和硬件联调问题的线索帮助读者建立从源码结构到实际调试的认知。1. 四路 SDI 采集卡黑屏三个月最后靠一份 gv7704 驱动包捋清了链路我见过最耗时间的一类板子是四路 3G-SDI 采集卡FPGA 侧的时序仿真全绿插上相机 BNC 就是不出图Linux 下连/dev/video都懒得给你建。问题往往不在 FPGA也不在内核版本而是夹在中间的那层 gv7704 驱动没人讲清楚——上电顺序是什么、SPI 用哪个模式、寄存器怎么分段、锁定状态从哪一位读。这份 gv7704 驱动包的价值就在这里它把从复位到出流的那串配置和判定条件摊开了不是丢一个能编过的.ko就完事。它适合正在做 SDI 采集、导播切换、工业视觉多路接入的嵌入式工程师也适合从 FPGA 转过来第一次写 V4L2 subdev 的同学。前提是你手上得有原理图还得有能测 SPI 波形的探头纯在软件层面照抄是跑不起来的。2. 先看清链路再动寄存器GV7704 的四通道结构与配置分段拿到一份驱动包最怕的是直接make然后贴进内核出不来图再回头翻代码。GV7704 这类四通道 SDI 接收器驱动能不能跑起来八成取决于你有没有先弄清它在整条链路里负责哪一段、寄存器分成几块、上电时序是什么样的。这三件事搞清楚后面写 probe 就是填空搞不清楚调一周也只是在“改了没反应”和“改坏了更没反应”之间来回。2.1 从 BNC 到 /dev/video驱动只管中间那一段一条完整的四路 SDI 采集链路责任是分层的。信号从 BNC 进来先过芯片内部的电缆均衡器和时钟恢复CDR再做解串和像素打包然后以并行或串行的方式送到 SoC 的视频输入接口最后由 DMA 搬到内存由 V4L2 暴露成设备节点。驱动真正管的是三件事把这些硬件按正确顺序点起来、把状态读出来告诉上层、把格式和时间戳报准。它不管均衡器怎么补偿、也不管 DMA 怎么排队那分别属于模拟前端和 SoC 侧的事。链路环节责任方出问题时的典型现象电缆均衡与自适应补偿GV7704 内部 EQ短线正常、长线掉锁误码计数飙升时钟恢复 CDRGV7704 内部完全锁不上状态位一直不翻转解串与像素打包GV7704 输出级出图花屏、半幅、颜色错位输出到 SoC 的走线板级 pinctrl 与硬件偶发丢行、DMA 溢出报错V4L2 节点与格式上报驱动有/dev/video但列不出 format帧缓冲与时间戳SoC 视频子系统掉帧、时间戳跳变、缓冲队列卡死我在排查时习惯先分层定位先确认芯片能不能读到正确的 ID再确认四路的状态位有没有翻转最后才去看/dev/video。这三步里任何一步失败后面的调试都没有意义——因为节点建出来了但没信号你根本分不清是驱动问题还是信号问题。2.2 寄存器不是一坨按顶层、通道、状态三段来读第一次打开这类芯片的寄存器表密密麻麻几百个地址很容易直接关掉。实际上它是有结构的我一般分成三段看。顶层段管全局芯片 ID、软复位、参考时钟分频、输出总线位宽和映射方式、全局中断使能。通道段是每路一份四路各有一套同样的配置管均衡器增益、CDR 使能、视频格式检测。状态段全部是只读的输出锁定标志、误码计数、检测到的分辨率和帧率驱动里轮询或中断处理读的就是这一段。分清楚之后驱动的结构也就跟着清楚了probe里只碰顶层段和复位v4l2的s_stream回调里碰通道段中断处理里只读状态段。很多人翻车是因为在中断处理里写寄存器或者把通道段当成全局段只配了一次结果四路里只有第一路出图。提示寄存器表里同名不同段的地址非常容易看串尤其是通道 0 和通道 1 的偏移量建议先在纸上把四路的基址写出来再写代码。2.3 上电与复位先供电、再给钟、最后放复位顺序错了会怎样最常见的现象是读 ID 全是0xFF或者全0x00你会以为是 SPI 不通其实是芯片根本没起来。正确的顺序是稳压器先稳定输出参考时钟起振并稳定再释放硬复位最后等内部 PLL 锁定一段时间这时候读寄存器才有意义。包里的参考代码给的最小延时比较激进我一般会把余量放大一倍因为不同板子的电源爬升速度差别很大。/* 上电初始化顺序任何一步顺序错了后面读 ID 全是 0xFF 或 0x00 */ static int gv7704_power_on(struct gv7704 *priv) { int ret; /* 1. 先给内核与 IO 电源具体电压由板级 regulator 决定 */ ret regulator_bulk_enable(ARRAY_SIZE(priv-supplies), priv-supplies); if (ret) return ret; /* 2. 参考时钟必须在复位释放前稳定晶振起振通常在毫秒级 */ ret clk_prepare_enable(priv-ref_clk); if (ret) goto err_clk; usleep_range(5000, 8000); /* 3. 硬复位低电平有效最小脉宽按手册实际给 10ms 更稳 */ gpiod_set_value_cansleep(priv-reset_gpio, 1); /* asserted */ usleep_range(1000, 2000); gpiod_set_value_cansleep(priv-reset_gpio, 0); /* released */ msleep(20); /* 4. 复位释放后再等内部 PLL 锁定这段等待不能省 */ msleep(50); return 0; err_clk: regulator_bulk_disable(ARRAY_SIZE(priv-supplies), priv-supplies); return ret; }这段代码里有两个参数值得单独说。第一个是usleep_range(5000, 8000)它等的不是“时钟稳定”这个抽象状态而是晶体振荡器的起振时间实际取决于你板子上用的是有源晶振还是无源晶体有源的可以更短但没必要省。第二个是最后的msleep(50)等的是内部 PLL 锁定如果你读 ID 时偶尔成功偶尔失败多半就是这里给短了。reset_gpio的极性交给gpiod的 flag 处理代码里不要自己再取反一次这是很常见的重复取反事故。3. 把节点跑出来SPI 配置、设备树挂载与 V4L2 验证寄存器结构清楚之后就该动手了。我一般不会一上来就写内核驱动而是先用spidev在用户态把读写打通——这一步能省掉大量“编译通过但没反应”的时间因为用户态的打印和试探都更快。确认寄存器读写正常、ID 对得上、四路状态位能翻转之后再把这套逻辑搬进内核 probe成功率会高很多。3.1 先用 spidev 把寄存器读写打通SPI 的模式是第一个坑。这类芯片常用的是模式 3CPOL1CPHA1但板级电平转换芯片和走线会影响边沿最终以逻辑分析仪抓到的波形为准。速率上先用 1MHz 跑通波形干净了再往上提别一上来就 10MHz读回值错位你会怀疑人生。#!/usr/bin/env python3 # 用 spidev 直接访问寄存器不依赖内核驱动先把读写链路打通 import spidev, time SPI_BUS, SPI_CS 1, 0 spi spidev.SpiDev() spi.open(SPI_BUS, SPI_CS) spi.max_speed_hz 1_000_000 # 先用 1MHz波形确认后再提高 spi.mode 0b11 # CPOL1 CPHA1多数此类接收器为模式 3 spi.bits_per_word 8 def wr(addr, val): addr 为寄存器地址帧格式地址高位|地址低位|数据写标志在最高位 spi.xfer2([(addr 8) 0x7F, addr 0xFF, val 0xFF]) time.sleep(0.001) # 单条写间隔批量写时可去掉 def rd(addr): r spi.xfer2([0x80 | ((addr 8) 0x7F), addr 0xFF, 0x00]) return r[2] def load_table(path): 按 地址 数据 的文本格式批量下载初始化序列# 开头为注释 with open(path) as f: for line in f: line line.split(#)[0].strip() if not line: continue a, v line.split() wr(int(a, 16), int(v, 16)) if __name__ __main__: print(chip id 0x%02x % rd(0x0000)) # 地址以包里 regmap 为准 load_table(./init_seq.txt) print(status0 0x%02x % rd(0x0200)) # 读回第一路的锁定状态代码本身不难关键在帧格式。上面按“地址高字节 地址低字节 数据”三字节来写如果你的板子实际是两字节地址或者带独立读写位从逻辑分析仪上抓一帧就能看出来写了什么、芯片返回了什么一比就清楚。load_table里的init_seq.txt就是从包里寄存器表导出的初始化序列一行一条方便你注释掉某几行做二分排查——这个手段比逐条改代码快得多。注意批量下载之后一定要做一次全量回读校验只写不读是排查中最容易放过的问题很多地址其实写失败了你并不知道。3.2 设备树与 probe复位、时钟、中断三件套搬进内核之后第一件事是把设备树写对。这类器件的节点通常挂在 SPI 控制器下面需要描述片选、SPI 模式、复位脚、参考时钟和中断脚。中断是很多人漏掉的一项——轮询也能工作但插拔和格式切换的响应会慢半拍做多路切换设备时体验很差。spi1 { status okay; gv7704: sdi-receiver0 { compatible vendor,gv7704; reg 0; spi-max-frequency 1000000; spi-cpol; /* 对应模式 3与用户态脚本保持一致 */ spi-cpha; reset-gpios gpio2 3 GPIO_ACTIVE_LOW; interrupts-extended gpio2 4 IRQ_TYPE_EDGE_FALLING; clocks clk_sdi_ref; clock-names ref; vdd-supply reg_1v2; vddio-supply reg_3v3; status okay; }; };spi-max-frequency我建议先跟着用户态脚本用 1MHz等整机稳定再往上调spi-cpol和spi-cpha必须和实测波形一致不能照抄别人的板子。compatible里的厂商前缀按你自己驱动的匹配表来写改的时候两边要一起改否则 probe 根本不会进来日志里也没有任何报错只在/sys/bus/spi/devices下看到一个没有驱动绑定的节点。3.3 用 v4l2-ctl 与 media-ctl 验证四路出流驱动 probe 成功之后不要急着写应用。先用标准工具确认节点、格式和数据流三件事都对。四路设备通常会枚举成四个 video 节点中间可能还有一个 media 设备把它们和 DMA 节点串起来先用media-ctl把拓扑打出来看清楚。# 1. 看媒体拓扑确认四路 sensor/subdev 到 DMA 节点的连接关系 media-ctl -p -d /dev/media0 # 2. 逐路确认支持的格式与分辨率 for i in 0 1 2 3; do v4l2-ctl -d /dev/video$i --list-formats-ext done # 3. 抓 200 帧看有没有实际数据流同时观察是否报超时 v4l2-ctl -d /dev/video0 \ --set-fmt-videowidth1920,height1080,pixelformatUYVY \ --stream-mmap --stream-count200 --stream-to/tmp/cap0.raw--stream-count给 200 是为了让问题暴露出来只抓一帧的话很多缓冲队列的问题根本来不及出现。--stream-to落盘之后可以用ffmpeg或者直接看文件大小估算帧率是否符合预期1920×1080 的 UYVY 一帧约 4MB200 帧约 800MB如果你的文件明显小很多说明中途丢帧了。media-ctl -p的输出里重点看 link 的ENABLED状态四路里只要有一路没 enable那一路上层是拿不到数据的。4. 避坑记录五条把我逼到重新焊板子的问题这一章是我自己踩过的、也在别人板子上复现过的五条。每一条都按“现象、原因、解决”写顺序基本就是排查时的推荐顺序。如果你现在正卡在某一步建议直接从现象对照着看。4.1 读 ID 全是 0xFF 或全 0x00现象驱动 probe 时读芯片 ID返回值恒为0xFF偶尔是0x00SPI 传输本身不报错。原因三种可能按概率排SPI 模式配错导致采样边沿不对MISO 没有上拉芯片未上电时总线浮空读成0xFF复位还没释放就开始读芯片根本没响应。解决先用万用表确认 MISO 在空闲时是高电平再用逻辑分析仪抓一帧看时钟和数据边沿的关系是否与spi.mode一致最后在代码里把复位释放后的等待时间加长到 50ms 以上重试一次。三步做完基本能定位到具体是哪一种。4.2 四路里只有第一路出图现象四路 BNC 都接了信号/dev/video0正常其余三个节点能打开但一直超时。原因通道段寄存器被当成全局段只配置了一次。四路各有独立的基址偏移初始化序列如果只按通道 0 的地址下载其余三路的均衡器和 CDR 实际上没有使能。解决把初始化表按“公共段 每通道段”拆成两份公共段下一次通道段循环四次、每次加通道偏移。改完用状态寄存器逐路读锁定标志确认不要只看有没有图。4.3 图像有横条纹、颜色整体偏绿现象能出图但画面有规律横纹颜色明显不对换一路信号症状相同。原因输出位宽映射和采集端不一致。这类芯片支持多种输出打包方式寄存器里配的是 20 位拆分模式SoC 侧却按 10 位单沿采样接收高低位对齐错了颜色分量自然全乱。解决对照原理图上数据线的实际连接顺序确认寄存器里的输出模式位再在驱动里把mbus_code报成与实际一致的类型。这个问题的特点是“图能出来但不对”很容易被误判成信号质量差先查映射再查线。4.4 热插拔之后不再重新识别现象上电时接了信号正常拔掉再插上节点还在但一直没数据重启才能恢复。原因中断处理函数里只读了状态没有清标志中断线一直保持有效电平后续的边沿进不来或者状态机里没有处理“信号丢失到重新锁定”的转换。解决在中断处理里先清中断标志再读状态并把锁定状态的变化做成状态机未锁定、锁定中、已锁定。每次进入未锁定就软复位通道段并重新下载该通道的配置这样插拔才能自愈。4.5 连续跑几小时后开始丢帧现象短时间测试完全正常拷机四五个小时后开始零星丢帧重启就恢复。原因温度上来之后参考时钟抖动变大内部 PLL 的锁定余量被吃掉或者驱动里的误码计数一直在涨但你从没看过等它涨到影响出图才发现。解决把误码计数和锁定状态定期打进日志加一个长时间跑的脚本盯着同时检查参考时钟的电源是不是和数字电源共用了滤波不好的支路。这一条最难查因为它不会立刻失败只会慢慢变差。# 拷机时同时盯三个东西节点是否还在、误码是否在涨、帧率是否稳定 while true; do dmesg -T | tail -n 5 v4l2-ctl -d /dev/video0 --get-dv-timings 2/dev/null sleep 60 done这个脚本很土但有用dmesg -T带上时间戳方便你把误码增长和温度、负载对起来看。真正定位问题还是要靠驱动里自己打的计数日志不要指望通用工具能告诉你芯片内部发生了什么。5. 从能出图到跑稳锁定余量、多路同步与长时间验证出图只是及格线。这类多路 SDI 设备真正难的是稳定性线缆长度不一样、信号源帧率不一样、环境温度会变任何一项都能让“昨天还好好的”变成“今天掉锁”。这一段讲的是我在项目里摸索出来的几个动作都是能直接落到代码和脚本上的。5.1 把“能锁”变成“锁得稳”读误码和均衡增益锁定标志只有 0 和 1 两个值它不会告诉你余量还有多少。真正有用的是误码计数和均衡器的当前增益档位增益已经顶到高位、误码在缓慢增长说明线缆或者连接器已经在临界点上了这时候就算当下能出图也不该放进出货名单。/* 周期性采集链路质量供上层做告警不要放在中断里做 */ static void gv7704_poll_quality(struct gv7704 *priv) { int ch; for (ch 0; ch GV7704_NUM_CH; ch) { u8 lock gv7704_rd(priv, GV7704_CH_BASE(ch) REG_LOCK_STATUS); u8 eq gv7704_rd(priv, GV7704_CH_BASE(ch) REG_EQ_GAIN); u16 err gv7704_rd(priv, GV7704_CH_BASE(ch) REG_ERR_CNT_H) 8 | gv7704_rd(priv, GV7704_CH_BASE(ch) REG_ERR_CNT_L); /* 只在状态变化或误码跨过阈值时打印避免刷屏 */ if (lock ! priv-ch[ch].last_lock || err priv-ch[ch].last_err 100) { dev_info(priv-dev, ch%d lock%u eq%u err%u\n, ch, lock, eq, err); priv-ch[ch].last_lock lock; priv-ch[ch].last_err err; } } }这里面REG_EQ_GAIN的档位含义要对着寄存器表看不同线缆长度下的正常值区间也不一样建议先在实验室用几根不同长度的线各跑一遍把基线记下来。误码阈值设 100 是个经验值太敏感会刷屏太迟钝又发现不了趋势。这个函数挂在定时器或者工作队列里周期一秒左右就够别放中断上下文。5.2 多路不同源时的帧率与时间戳四路接不同相机的时候帧率可能各不相同有的 50i 有的 60p还有的源在切换分辨率。驱动侧最忌讳的是把四路当成同一个时钟域处理时间戳如果用同一个基准又不对齐应用层做多路合成时会出现某一路持续偏移。我的做法是在每路的状态机里独立维护“当前检测到的格式”格式变化时通过 V4L2 事件上报而不是让应用去轮询。时间戳统一用ktime_get_ns()在帧起始中断里打不要在 DMA 完成回调里打因为 DMA 完成的时间受队列深度影响会把抖动放大。5.3 长时间验证写个能跑的拷机流程稳定性不是测出来的是熬出来的。我会准备一个最小拷机流程四路同时开流、每路各自落盘一小段、跑满 24 小时中途记录误码、丢帧、节点状态。跑完之后核对每个文件的帧数差一帧都要查清楚。# 四路并行开流 24 小时每路只落 10 秒数据避免磁盘写满 for i in 0 1 2 3; do ( for r in $(seq 1 1440); do v4l2-ctl -d /dev/video$i --stream-mmap --stream-count300 \ --stream-to/tmp/ch$i.raw 2/tmp/ch$i.log sleep 50 done ) done wait--stream-count300在 60fps 下约 5 秒加上sleep 50一小时跑一轮24 小时共 1440 轮。每轮的 stderr 都追加到日志里超时和丢帧的报错会留在里面事后 grep 一次就能看出问题出现在第几轮。这个流程的价值在于它足够长能覆盖温度变化和内存碎片累积这类只在长时间运行后才暴露的问题。6. 进阶把初始化序列做成可回读自检的一步流程调通之后最常见的退化是换了一块板子、换了一批芯片原来的初始化序列里某几条不再适用但因为代码里只是“写进去”没人知道到底有没有生效。我的习惯是把整个初始化过程改成“写一遍、读一遍、对一遍”任何一条回读不一致就直接报错退出而不是继续往下跑。具体做法是从包里的寄存器表生成一份带校验的配置清单格式是“地址、期望值、掩码”三列掩码用来处理那些只关心某几位的寄存器。加载时先写再读用掩码比较不一致就打印出地址和实际值。#!/usr/bin/env python3 # 初始化序列自检写一条读一条掩码比较不一致立即停下 def apply_and_verify(spi, table_path): fails 0 with open(table_path) as f: for lineno, line in enumerate(f, 1): line line.split(#)[0].strip() if not line: continue parts line.split() addr, val int(parts[0], 16), int(parts[1], 16) mask int(parts[2], 16) if len(parts) 2 else 0xFF wr(addr, val) got rd(addr) if (got mask) ! (val mask): print(line %d addr 0x%04x expect 0x%02x mask 0x%02x got 0x%02x % (lineno, addr, val, mask, got)) fails 1 print(verify done, %d mismatch % fails) return fails掩码这一列是关键。有些寄存器是自清零的有些位是只读状态写进去再读回来必然不一致没有掩码的话你会被一堆假失败淹没。我一般把可写且稳定的位设为0xFF混合寄存器按位定义拆开写比如0x0F只校验低四位。校验项期望结果不一致时的排查方向芯片 ID与手册一致SPI 模式、供电、复位时序顶层配置回读与写入值按掩码一致地址偏移算错、写保护未解除四路通道配置四路各自一致通道基址偏移、循环变量写错锁定状态接入信号后置位均衡器未使能、线缆或连接器误码计数稳定不增长参考时钟质量、电源纹波这套流程跑顺之后我给自己定了个规矩每次换板子、换芯片批次、改设备树都先跑一遍自检脚本再谈功能。它花不了两分钟但能挡掉后面几天的“明明代码没改为什么不出图”。从那以后我每次拿到新的板子第一件事都不是编内核而是把这份自检跑通——顺序对了后面的事才谈得上效率。希望帮到你。本文还有配套的精品资源点击获取