ARTICLE DETAIL

资讯详情

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

基于FUSB302的Linux PD快充方案:硬件设计、驱动调试与排障指南

基于FUSB302的Linux PD快充方案:硬件设计、驱动调试与排障指南 做嵌入式设备的人应该都有同一种感受Type-C口想真正跑出PD快充难点从来不在调一个寄存器或者抄一段代码而是把CC检测、角色识别、电源协商、VBUS控制这一整条链路理清楚。FUSB302这颗芯片的定位很有意思它把所有Type-C物理层和PD协议物理传输的事情都接了过去留给主控的只有一个I2C接口和一根中断脚。这篇东西基于我自己在i.MX与RK平台上用FUSB302做PD Source、PD Sink以及拓展坞供电的实际经历把从画原理图、写设备树、确认通信、看驱动日志到最终测出9V/1.5A、12V/1.5A这类协商结果的完整路径讲一遍。适合正在做Type-C供电设备、电池充电管理、PD拓展坞或者单纯想把普通Type-C口升级成可协商快充口的朋友不管你用的是NXP、瑞芯微还是全志、STM32MP1能跑Linux就能参考。1. 为什么是FUSB302把CC检测、角色识别和PD物理层一次打包的方案1.1 PD快充里MCU最头疼的部分其实不是协议栈很多人第一次接触PD快充第一反应是去研究PD协议栈怎么写比如Source Capabilities消息怎么组、Request消息怎么解析、Hard Reset怎么处理。做了一圈之后才意识到真正烦人的是底层那一堆模拟前端的事情。CC引脚上要监测两个比较器电压用于判断设备是DFP主设备、电源提供方、UFP从设备、电源接收方还是DRP双角色不同电压区间对应不同的角色和电流能力。同时CC线上做BMC信号时要处理定时、脉冲宽度、双相编码、线缆补偿等一堆物理层细节。这些如果用MCU的GPIO和定时器去模拟调试一次就够喝一壶的。FUSB302的价值就在这里它内部集成了CC引脚比较器、BMC收发器、VBUS监测、以及一组用于控制外部路径开关的逻辑主控通过I2C写几个寄存器和读中断状态就能拿到完整的Type-C/PD底层信息。对内层面主控不再需要精确到微秒级去操作GPIO翻转因为BMC物理层和收发缓冲都在FUSB302里完成。对外层面CC引脚上该有的上拉/下拉电阻、死电池上拉、VCONN开关这些也都可以由芯片的内部结构配合少量外部器件完成。你真正要自己做的是把I2C调通、接好中断线、把VBUS路径上的开关管选对。1.2 FUSB302在典型系统里的位置一个以Linux主控为核心的PD快充系统通常长这样Type-C座子的CC1/CC2接FUSB302的CC1/CC2引脚FUSB302通过I2C接主控的I2C控制器FUSB302的INTB中断输出低有效接主控的一个GPIO用于通知主控有事件发生VBUS路径上有一个高边开关管比如负载开关或带路径管理的芯片开关控制脚由主控GPIO或FUSB302的GPIO输出控制VBUS电压检测连接到FUSB302的VBUS感知引脚让芯片能识别电压状态这种架构下FUSB302承担了三个角色Type-C连接检测器、PD BMC物理层收发器、VBUS信号采集器。Linux内核里的TCPMType-C Port Manager框架则负责上层策略读到CC电压之后判断设备类型收到Source Capabilities之后决定要不要申请更高电压DPMDevice Policy Manager来决定具体请求9V还是5V还是12V。FUSB302驱动只是TCPM框架的一个底层实现。2. 硬件连接从Type-C座子到FUSB302再到主控的实际走线2.1 引脚速览与最小原理图设计FUSB302常见封装是QFN14引脚不多但每一个都不能大意。我按最小系统列一份引脚名称作用连接建议1VIN3.3V供电输入接3.3V就近放0.1uF退耦电容另外至少预留4.7uF容值用于瞬态2VDDIOIO电源与主控IO电平一致通常3.3V如果主控IO是1.8V可以接1.8V3I2C_SCLI2C时钟接主控I2C SCL上拉电阻典型2.2k到VDDIO4I2C_SDAI2C数据接主控I2C SDA上拉电阻同样2.2k5INTB中断输出低有效开漏接主控GPIO配置为输入、下降沿或低电平触发外部加上拉到VDDIO6VBUSVBUS电压感知通过电阻分压接VBUS总线分压比参考数据手册7CC1Type-C CC1直接连Type-C座子CC1注意串接电阻位置8CC2Type-C CC2直接连Type-C座子CC29GND地铺地处理减少回流路径10GPIO可配置GPIO可选常用于控制VBUS开关或LED11GPIO可配置GPIO可选12DPD/D-数据通道开关控制——不FUSB302这块DP/DM不是给USB2.0数据用的是芯片加强/检测用途通常悬空这里有一个容易混淆的细节FUSB302主要负责CC和PD BMC它不处理USB2.0的D/D-信号。如果你要做的是带数据传输的Type-C口D/D-需要单独走USB2.0线并配合CC逻辑决定是否切换上下拉。FUSB302只是把DP/DM引脚做了一组通用输入输出一般应用可以悬空。2.2 PCB布线与供电要注意的事CC走线CC1和CC2是信号质量敏感线路PD协商时BMC信号大约在300kbps级别看起来频率不高但CC引脚上的电压比较结果直接决定角色判断。走线尽量短而直远离高频开关节点和电感。Type-C座子到FUSB302的CC引脚之间串接4.7k到10k的电阻是常见做法用于ESD保护异常电压情况下的限流具体阻值参考FUSB302数据手册建议。I2C走线I2C的SCL/SDA上拉电阻要靠近FUSB302引脚不要离得太远。如果主控I2C总线上还挂了其它器件注意总线电容FUSB302的I2C最高可跑到1MHz但因为通常和多个从设备共享一条总线工作频率一般设400k。电源方面VIN和VDDIO在大多数设计中直接接同一个3.3V但要注意VIN的瞬态电流在PD协商切换电压时会明显波动退耦电容不要省。VBUS感知分压电阻的精度会影响VBUS监测阈值用1%精度电阻。提示FUSB302的INTB是开漏输出外部上拉电阻一定不能忘。很多板子调试时I2C一切正常就是中断触发不了或者不响应十有八九是漏了这颗上拉。2.3 上电自检怎么确认硬件是好的在跑Linux驱动之前强烈建议先用简单的I2C工具或者最小固件程序做一次硬件自检把问题隔离在底层。具体操作上电用万用表确认FUSB302的VIN和VDDIO电压正常3.3V别偏差超过5%。示波器看CC1/CC2引脚上的静态电平。此时如果没有插入任何设备且FUSB302配置为Source模式CC引脚上应该能看到一个由芯片内部上拉产生的电压通常接近0.7V到1.2V范围具体取决于FUSB302内部电流源设置和外部电阻。用I2C工具读取Device ID寄存器地址0x01正常返回0x81左右的值不同批次可能略有差别但高字节的device family id应该一致。INTB引脚在无事件时应该是高电平触发事件时拉低。如果你发现CC引脚电压量出来完全为零而I2C通信正常先去查FUSB302的开关寄存器0x02里MEAS_CC1/MEAS_CC2有没有启用或者芯片是不是不小心进了Standby模式。这一步能用示波器量出来比在Linux日志里瞎猜强得多。3. Linux侧的准备内核选项、设备树与I2C通信验证3.1 内核里要打开哪些配置Linux内核自4.x之后就在drivers/usb/typec/tcpm/下面有完整的FUSB302驱动实现fusb302.c基于TCPM框架。这比从头写一个驱动要靠谱得多因为它把TCPCType-C Controller的抽象层、端口角色管理、PD协议消息转发都做完了。你需要确认内核配置里有CONFIG_USB_TYPECy CONFIG_USB_TYPEC_TCPMy CONFIG_USB_TYPEC_TCPCIy CONFIG_USB_TYPEC_FUSB302y CONFIG_USB_ROLE_SWITCHy CONFIG_USB_PD_DPy有些内核版本把TCPCI和FUSB302分开FUSB302是独立的config项别只开TCPCI就把FUSB302漏了。如果你用的是老内核比如4.9之前可能需要把TCPM相关补丁backport。实际项目里我遇到过在vendor SDK中发现CONFIG_USB_TYPEC_FUSB302没有打开导致设备树里挂上但驱动加载不上的情况排查方法很简单查看内核目录里有没有生成对应的.o文件即可。如果PD中包含DisplayPort功能还需要CONFIG_TYPEC_DP_ALTMODE。不过这里只聊纯充电的情况。3.2 设备树里怎么描述这颗芯片以i.MX8M系列为例假设FUSB302挂在i2c2上GPIO1_IO03作为中断。设备树节点写法一般是i2c2 { clock-frequency 400000; pinctrl-names default; pinctrl-0 pinctrl_i2c2; status okay; fusb302: typec22 { compatible fcs,fusb302; reg 0x22; pinctrl-names default; pinctrl-0 pinctrl_fusb302_int; interrupt-parent gpio1; interrupts 3 IRQ_TYPE_LEVEL_LOW; fcs,port-type source; fcs,typec-power pd; fcs,max-sink-mv 20000; fcs,max-sink-ma 5000; fcs,max-sink-mw 100000; vbus-supply reg_vbus; status okay; }; };几个属性说明reg 0x22FUSB302的7位I2C地址是0x22。这是固定地址无法通过引脚修改。fcs,port-type可选source、sink、dual。如果做的是拓展坞电源输入口一般设dual或者sink如果是给设备供电的口设source。fcs,max-sink-mv/ma/mw这是Sink模式下允许请求的最大电压/电流/功率。设太高可能会导致策略协商时报错设太低又浪费了适配器能力按实际硬件设计能力来。interrupts 3 IRQ_TYPE_LEVEL_LOWINTB是低有效用LEVEL_LOW比EDGE_FALLING更稳妥可以避免边沿丢失。但要注意如果是电平触发中断处理函数不能太慢否则会一直触发。3.3 用i2c-tools确认芯片与主控通信正常设备树挂上去之后先别急着看PD协商。确认I2C通路是最基本的一步。在Linux终端执行i2cdetect -y 2正常情况下0x22位置会出现UU或者22。出现UU表示该地址已被内核驱动占用22表示没有驱动占用、但芯片能响应地址探测。如果什么都没显示优先检查I2C引脚是否错位、上拉电阻是否正确。如果I2C总线上恰好没有其它设备竞争也可以直接i2cget读寄存器i2cget -y 2 0x22 0x01返回结果应该是一个非0值。这个值如果是0xff通常说明芯片没有应答或者SDA被拉死如果是0x81左右说明Device ID正常。看驱动是否成功probedmesg | grep -i fusb302能看到类似fusb302 0-0022: fusb302_probe: probe success或者切换到TCPM模式的日志说明驱动加载成功。4. 驱动调试读懂寄存器、中断与PD协商过程4.1 FUSB302寄存器地图常用寄存器逐个过FUSB302寄存器数量不多但每个都决定了关键行为。我把调试中最常碰到的列出来地址名称关键位调试用途0x01DeviceID芯片版本确认I2C通信正常0x02SwitchMEAS_CC1, MEAS_CC2, VCONN_CC1/CC2切换CC测量通道、VCONN输出0x03MeasureMDAC值, MEAS_VBUS配置比较器阈值、触发VBUS测量0x06Control0HOST_CUR, TX_START, RESET主机电流设置、发送启动、芯片复位0x07Control1ENSOPC, ENSOPC_DB, TX_DMA_ENSOP类型使能调试PD消息时必须注意0x08Control2TOGGLE, WAKE, MODE角色切换使能DRP模式会自动切换角色检测0x0AMask各中断屏蔽位屏蔽不需要的中断源0x0CResetPD_RESET, SW_RESET软复位芯片0x1CStatus0aBC_LVL, VBUS连接检测状态0x1DStatus1a当前角色的详细状态判断是source还是sink0x1EInterrupta中断源调试中断风暴的关键0x2AFifosTXFIFO/RXFIFO读写PD消息FIFO调试中最容易忽略的是Control1寄存器0x07中的SOP类型使能位。PD消息分SOP、SOP、SOP普通设备之间的协商用SOP就够了但如果要支持线缆电子标记查询E-Marker需要使能SOP。如果用默认配置FUSB302收到SOP消息时不会正确响应排查半天往往都在这里。4.2 中断驱动的上报机制FUSB302把几乎所有事件都通过INTB脚上报你只要维护好一个中断线程在里面读Interrupt寄存器和Status寄存器然后按事件类型喂给TCPM状态机就行。内核中的fusb302驱动就是这样实现的。实际调试时要关注中断类型BC_LVL_CHANGECC引脚电压变化通常是插入或拔出事件TOGDONE角色检测完成DRP模式下切换角色时会出现VBUS_CHANGEVBUS电压变化RX_SOP收到PD消息TX_SUCCESS / TX_FAILED发送消息的结果调试中断风暴问题有个很实用的技巧在中断处理函数入口读一次Interrupta寄存器打印二进制值到dmesg连续插拔几次Type-C线缆观察中断源的变化是否符合预期。如果某个中断位始终置位且无法清除大概率是硬件状态没满足比如CC引脚悬空导致比较器一直在翻转。4.3 完整PD协商调试日志分析在PD协商正常的情况下dmesg里会出现类似这样的关键日志usb tcpm_port0: PD RX, header: 0x2b1: 0:1, 0:0 [0] usb tcpm_port0: PD RX, SRC_CAP: 0 caps usb tcpm_port0: PD TX, header: 0x1101: 1:0, 0:0 usb tcpm_port0: PD TX, REQUEST: 5000mV 3000mA usb tcpm_port0: PD RX, header: 0x0501: 1:1, 0:0 usb tcpm_port0: PD RX, ACCEPT usb tcpm_port0: PD RX, header: 0x0203: 1:1, 0:0 usb tcpm_port0: PD RX, PS_RDY usb tcpm_port0: PD TX, GOTOMIN这段日志说明Sink端收到了Source的SrcCap然后发出了Request请求5V/3ASource接受后发送PS_RDY。如果卡在SRC_CAP: 0 caps不往下走说明FUSB302收到了消息但解析不出PDO要检查Control1的SOP类型使能以及FIFO读取时序。如果你想做的是Source端给别的设备供电日志会有些不同。当Sink接入并被识别后Source端必须主动发送Source Capsusb tcpm_port0: PD TX, header: 0x2b1: 1:0, 0:0 usb tcpm_port0: PD TX, SRC_CAP: 1 caps usb tcpm_port0: PD RX, header: 0x1101: 1:0, 0:0 usb tcpm_port0: PD RX, REQUEST: 5000mV 3000mA usb tcpm_port0: PD TX, ACCEPT usb tcpm_port0: PD TX, PS_RDY如果Source端始终不发送SRC_CAP先确认端口策略里有没有把fcs,port-type设为source或dual再看CC引脚比较器有没有识别到Sink的Rp/Rd状态。调试PD协商最有效的工具是逻辑分析仪最便宜的那种采样率就够接FUSB302的CC1线用BMC解码器或者直接看波形特征确认消息内容和日志是否一致。我印象最深刻的一次日志显示Source已发出PS_RDY但充电器仍然不给电逻辑分析仪一抓才发现PS_RDY的BMC脉冲宽度略有偏差导致对端不认为这是一个合法消息——这种问题只能靠波形去找。4.4 通过sysfs验证输出能力Linux的TCPM框架会把Type-C端口信息暴露在sysfs这是验证驱动工作的最直接途径ls /sys/class/typec/port0/ cat /sys/class/typec/port0/data_role cat /sys/class/typec/port0/power_role cat /sys/class/typec/port0/port_type插入PD充电器后会多出一个partner节点cat /sys/class/typec/port0/partner/supply/0/current_now cat /sys/class/typec/port0/partner/supply/0/voltage_now这两个值显示当前协商到的电压和电流与PDO中的值对应。如果能看到voltage_now变成9000mV说明9V档位协商成功。有些平台需要CONFIG_TYPEC_DP_ALTMODE或具体平台的供电策略配合sysfs才完整。如果电源协商成功但实际电流上不去问题往往不在FUSB302而在更高层。比如平台PMIC里的充电策略限制、电源路径开关管的导通电阻过大、或者VBUS走线太细。FUSB302只能把协商结果放到寄存器里真正的电流通路是你自己的硬件。5. 调板过程中反复踩到的坑5.1 I2C读写正常但中断不触发或者中断风暴无休止这是一个非常典型的FUSB302问题。I2C通信正常说明芯片活着但插拔线缆时INTB引脚没有变化排查方向检查INTB外部上拉。没有上拉引脚浮空中断状态无法被主控正确识别。检查中断配置是否用了下降沿触发。FUSB302的INTB在事件发生时拉低事件没被清除前一直为低。如果配置成边沿触发驱动里只清了一次中断下次事件就会被漏掉。建议配置成LEVEL_LOW并在中断线程里循环读到中断寄存器为空为止。中断风暴的时候往往是Mask寄存器0x0A没有屏蔽不需要的中断源比如CC电压毛刺触发BC_LVL_CHANGE或者VBUS抖动触发VBUS_CHANGE。调试时先把Mask全部置1只放开当前关心的中断源然后一个一个加。最关键的一点每完成一个事件处理必须把引起中断的Status位清掉。FUSB302的中断源在Status/Interrupt寄存器中读取之后写特定值清除有些驱动实现如果不读完全部事件芯片会一直拉低INTB。5.2 CC引脚电压不对导致角色协商失败CC引脚上的电压是整个Type-C连接检测的根本。FUSB302内部有比较器但外部电阻网络会影响分压结果。常见故障现象是接线正确、I2C正常、但TCPM日志显示CC1: 0, CC2: 0也就是检测不到任何连接。排查步骤用万用表量Type-C座子CC1与GND之间的电压。Source模式未连接时应该有上拉电压通常在0.7V~1.2V之间具体取决于Rp设定。如果量到0V检查FUSB302的MEAS_CC1对应的内部上拉有没有打开。插入一个标准的UFP设备比如带下拉电阻的转接板CC1应该被拉到0.2V~0.6V区间对应Rd下拉。FUSB302的比较器就能判定为UFP。如果插入后电压区间仍然偏高可能是外部串联电阻太大改变了分压比。我之前用过一颗10k串联电阻结果Rp和Rd分压后正好落在两个比较器阈值之间FUSB302判定结果反复横跳。换成数据手册推荐的阻值就正常了。注意Type-C规范里Rp和Rd的电压区间和电流能力是强相关的。Source的Rp对应的默认电流能力有USB 2.0 500mA、1.5A、3A三个档位由Rp电阻值决定。FUSB302在source模式下是根据你配置的Control3寄存器的电流能力来设定内部Rp的别外部再并一路电阻否则会改变电压区间。5.3 PD协商失败时先别急着怀疑驱动源码PD协商失败日志里出现各种错误很多人第一反应是驱动有问题实际上硬件层面的坑远多于软件。我的排查顺序是确认电压域。PD协商时VBUS可能先从5V升到9V或者先降到0V再重新拉起。用示波器观察VBUS波形看切换波形是否陡峭、有没有明显过冲。确认VBUS放电。PD规范要求电压切换前要放电。如果硬件上没有放电回路或者放电时间太短对端会认为你是非法切换直接拒绝后续协商。FUSB302没啥好办法这是外部电路设计问题。抓RX FIFO。如果收到消息但解析出来的header永远不对检查I2C时钟频率是不是太高。FUSB302的I2C最高支持1MHz但布线比较长或者有其他负载时400k更安全。一次调试中我把I2C频率从400k改到1MHz后RX FIFO几乎全是乱码降到400k就好了。对照PD规范看日志里的state。TCPM框架状态机里有明确的状态迁移比如SNK_READY、SRC_READY、HARD_RESET等。如果卡在某个状态先查对应状态的入口条件是否满足。5.4 拓展坞场景下D/D-和CC的关系既然搜热词里出现了pd快充拓展坞就多提一句。拓展坞的Type-C口通常既要传数据又要供电很多人被绕晕的地方在于USB数据线的D/D-和CC协议是两套独立的东西。FUSB302管的是CC和PD协商D/D-由USB2.0 PHY负责。做拓展坞时CC逻辑决定的是这个口当前是DFP还是UFP进而控制D/D-信号是否连接到主机端。如果CC协商成功但数据传输失败多数是D/D-通路上的切换开关没有正确联动而不是FUSB302的问题。另外拓展坞如果支持PD Sink充电口一定要让FUSB302工作在dua l或者sink模式并正确配置max-sink-mv/ma/mw否则它只能看到5V的固定供电永远不会向适配器请求9V/12V。6. 调试工具与现场排查方法补充6.1 除了dmesg还能怎么看状态Linux内核TCMP框架提供了debugfs接口调试PD协商的非常有用。挂载debugfs后mount -t debugfs none /sys/kernel/debug cat /sys/kernel/debug/usb/tcpm/port0这个文件会输出当前端口状态、role、negotiated current/voltage以及最近一次接收到的PDO列表。比dmesg里翻日志要清晰很多。很多vendor SDK编译时默认打开。6.2 修改PDO列表如果是作为Source给别的设备供电默认输出的电压档位由驱动和策略层决定。在设备树里或者驱动的tcpm_config中可以定义自己的PDO数组。比如只输出5V和9V两档Core里的port_type设为sourcePDO设置为fixed 5V/3A和fixed 9V/3A。修改后重新编译内核插入支持PD的Sink设备能看到Sink收到了两组PDO然后结合它自己的需求请求其中一档。调试PDO时建议用USB PD协议分析仪或者至少用一个支持不同电压请求的诱骗器。诱骗器可以把Sink能力固定为9V/3A用来验证Source端是否真的能升到9V。我个人在实际操作中还有一个习惯每次改完设备树或内核配置先花两分钟用i2cget读一遍FUSB302的Device ID。这样做不是为了确认硬件又活了而是确保芯片确实从复位状态被正确初始化了。很多改了配置不起作用的问题其实是设备树文件没被重新编译进boot镜像或者i2c节点alias冲突导致FUSB302一直没被probe。FUSB302这颗芯片本身非常成熟大多数调不通的项目最后要么死在硬件细节要么死在条件编译和配置管理上真正需要怀疑芯片本身的情况少之又少。如果你正被PD快充整得焦头烂额按我上面这套顺序走一遍先确认硬件电压和CC波形再确认I2C能读到Device ID然后看dmesg里TCMP状态机的输出最后再动PID配置。每一步都能明确结论的话PD快充基本不会成为你项目里过不去的坎。
返回列表