☰
RK3588 Type-C PD充电调试实战:DTS配置与内核驱动全解析
2026/9/29 19:59:55 网站建设 项目流程

最近在调一块 RK3588 平台的 Android 12 整机,第一块硬骨头就是 Type-C口的 PD 充电。表面上看,不就是个充电口嘛,接上充电器能用就行。真调起来才发现,插上支持 PD 的氮化镓充电器,系统能识别有外接电源,但电流死活锁在 5V/1.5A;换一台机器又能协商到 20V/5A;再换一根线,直接没有触发 CC 中断,整机完全没反应。这中间的差异,基本都出在 DTS 配置和内核驱动处理上。

这篇文章围绕 [RK3588-Android12] 平台上 USB Type-C PD 充电这条完整链路展开,把设备树里 PHY、TCPC/TCPM、charger 这三者的关系讲清楚,再带上实际改过的内核驱动代码思路,最后把调试过程和踩过的坑整理成速查表。不管你是正在给手头 RK3588 开发板添加 PD 支持,还是在定制 ROM 里完善 Type-C 功能,这篇都值得收藏对着抄。

1. 先搞清楚 RK3588 上 Type-C PD 充电的整体链路

在动 DTS 之前,必须先理解 RK3588 这颗 SoC 在 Type-C PD 充电里到底扮演什么角色,以及外围芯片需要做什么。很多人上来就改 DTS,改完发现没效果,大概率是因为没搞清“谁在协商 PD 协议”“谁在管 VBUS”“谁在告诉充电芯片要输出多少伏”。

1.1 USB Type-C 物理层与 PD 协议栈的分工

RK3588 本身集成了多个 USB 控制器和 PHY,包括 USB 2.0 PHY(用于 OTG/主机检测、BC1.2 检测)和 USB 3.0/3.1 combo PHY(用于 SuperSpeed 数据传输)。但 Type-C 的口子要支持 PD 充电,光靠 SoC 内部 PHY 是不够的,因为 PD 协议跑在 CC(Configuration Channel)线上,这是一根独立的单线协议,由 TCPC(Type-C Port Controller)芯片负责收发 BMC 编码信号。

实际硬件链路上,USB Type-C 插座上的 CC1/CC2 引脚会连接到一颗 TCPC 芯片,比如 FUSB302、HUSB311、TUSB422 这类,TCPC 再通过 I2C 与主控通信。RK3588 主控这边运行的是 TCPM(Type-C Port Manager)软件栈,负责策略决策,比如要不要进入 PD 协商、请求多大的电压电流、如何切换 Device/Host 模式。简单类比:TCPC 是“收发信使”,只负责按协议把消息发出去、收回来;TCPM 是“决策大脑”,决定给充电器发什么请求,拿到结果后怎么交给充电芯片。

DTS 里要做的事,就是把这几个环节的拓扑关系描述清楚:TCPC 挂在哪条 I2C 上、中断脚是哪个、PHY 怎么跟 TCPC 对应、充电器和电池节点如何接收协商结果。任何一环没有对齐,都会导致 PD 协商失败或充电电流上不去。

1.2 DTS 里需要关注的三个关键对象

在 RK3588 的 BSP 设备树里,围绕 Type-C PD 充电主要有三个对象,这三个对象如果没配对,整个充电链路就是断的:

第一个是 USB PHY 节点。RK3588 的 usb2phy 提供 OTG 检测和 BC1.2 检测能力,USB3 的 combo PHY 负责高速数据通道,这些节点里通常还引用了extcon,用来做角色切换和充电检测的通知机制。

第二个是 TCPC 控制器节点。它通常挂在某个 I2C 总线下面,compatible 对应具体的 TCPC 芯片型号,包含 reg 地址、中断 GPIO、pinctrl、以及 vbus-supply 电源属性。TCPM 通过这个节点与 TCPC 建立联系。

第三个是充电器/电池节点。RK3588 配套的 PMIC(比如 RK806),以及外置充电 IC(比如南芯 SC8886、英集芯 IP2326 之类)会注册为 power_supply 设备。PD 协商出来的电压电流,最终要写进这个充电节点,否则 PMIC 只会按默认的普通 USB 充电方式走 5V/1.5A 甚至 500mA。

DTS 设计思路的核心,就是把这三个对象通过extcon、phandle、ports/endpoint等机制串联起来。下面我会逐个节点展开讲,并给出可直接参考的配置片段。

2. DTS 关键节点逐项解析

这一节是全文的实操基础。我以 RK3588 常见的 EVB 设备树为蓝本,把几个核心节点的配置方式和注意事项拆开讲。注意,不同 SDK 版本有些节点名和属性名会有细微差异,但核心逻辑一致,你只需要在具体代码里搜索同名节点替换即可。

2.1 usb2phy 节点中的 Type-C 相关配置

RK3588 的 DTS 里,usb2phy 节点一般长这样:

&usb2phy0 { status = "okay"; }; &u2phy0_otg { vbus-supply = <&vcc5v0_otg>; rockchip,vbus-always-on; status = "okay"; };

这里有几个细节值得注意。u2phy0_otg节点对应 OTG 口,系统通过它检测外部设备的插入,同时用来做连接器角色切换。vbus-supply是 VBUS 的电源对象,在 Device 模式(被充电)下,这个电源通常由外部提供,驱动会读取它的状态来判断是否已经插入 VBUS;在 Host 模式下,则由主板主动输出 VBUS。

rockchip,vbus-always-on这个属性看名字就知道,是不管角色怎么切,VBUS 一直保持输出。但有些方案会把 Type-C 做 DRP(双角色端口),既要能充电也要能对外放电,这时候一般不建议直接vbus-always-on,而是让 TCPM 根据角色切换动态控制 VBUS,否则可能出现“Device 模式下还在往外吐电”的尴尬情况。

第二个需要关注的是 usb2phy 里的extcon引用,常见写法是在 USB 3.0 控制器节点里引用:

&usbdrd3_0 { status = "okay"; extcon = <&usbdrdphy0>; }; &usbdrdphy0 { status = "okay"; extcon = <&usbdrdphy0>; };

这段配置把 USB3 controller 与物理层驱动绑定在一起,extcon用来上报 ID/充电检测事件。Android 上层就是通过 extcon 的变化,感知到“这是一个 USB 充电器还是一个 Type-C 耳机”等设备类型。如果这里没有配对,最典型的症状是:插入充电器后,log 里能看到 TCPC 的 CC 中断,但是 Android 系统里的sys/class/power_supply/usb/type始终显示 N/A,充电图标也不出来。

2.2 外接 TCPC 控制器的 DTS 节点

RK3588 的 TCPC 芯片一般挂在 I2C 上,下面是一个常见的 FUSB302 节点配置,很多国产板子直接照抄这个模板:

&i2c6 { status = "okay"; fusb0: fusb30x@22 { compatible = "fcs,fusb302"; reg = <0x22>; interrupt-parent = <&gpio3>; interrupts = <RK_PB5 IRQ_TYPE_LEVEL_LOW>; pinctrl-names = "default"; pinctrl-0 = <&fusb0_int_l>; vbus-supply = <&vbus5v0_typec>; status = "okay"; ports { #address-cells = <1>; #size-cells = <0>; port@0 { reg = <0>; tcpm_typec0: endpoint@0 { remote-endpoint = <&typec0_ep>; }; }; }; }; };

reg = <0x22>是 TCPC 芯片的 I2C 地址,不同的 TCPC 可能用不同地址,需要查芯片手册。interrupts = <RK_PB5 IRQ_TYPE_LEVEL_LOW>是 CC 中断脚,这个是排查“插拔没反应”的关键。很多板子的 TCPC 中断脚和别的外设复用了,导致中断信号被占用,CC 检测就一直收不到。

ports/port@0/endpoint这一段是 Type-C 图形框架的绑定方式,用来把 TCPC 和 USB controller 的 typec 端口连接起来。如果你的 BSP 里没有这段,也可以工作,但部分依赖 typec class 的 Android 功能可能没法完整运行。

从硬件调试经验来看,TCPC 节点最常踩的坑有三个:I2C 地址不对导致驱动 probe 失败,中断 GPIO 被复用导致插拔无响应,pinctrl 里的中断引脚上下拉配置不对导致一直误触发中断。前两个靠对照原理图就能解决,第三个需要抓cat /proc/interrupts看看中断是不是疯狂增加。

2.3 extcon、TCPM 与充电器的关联

TCPC 驱动跑起来后,内核里会注册一个 Type-C port,并且通过 typec class 上报状态。RK3588 平台为了能对 PD 协商做策略控制,BSP 里一般会额外带一个tcpm_policy或者名为rockchip,tcpm-policy的节点,用来配置 PD 策略参数:

tcpm_policy { compatible = "rockchip,tcpm-policy"; tcpm_policy_support; pinctrl-names = "default"; ... };

这个策略驱动的本质,是让 PD 协商不单靠内核 tcpm 默认逻辑,而是允许厂商限定电压电流范围。比如你的板子只能承受 9V/2A,不想让充电器协商到 20V/5A,就在这个节点里把 max 电压电流写死。后面我会在第 3 节专门讲怎么改。

充电器节点也需要仔细确认。RK3588 EVB 上常用 RK806 PMIC,PMIC 内部集成了充电功能,DTS 里会注册类似rk806_charger的节点;如果是外置充电 IC,则要确保它的 I2C 地址和中断配置正确。关键在于,PD 协商出的结果要能传递到充电 IC。内核里常见的传递路径是:tcpm 驱动获取到 PDO 之后,调用power_supply_set_property或者直接通过charger_manager更新充电电流电压参数。

2.4 DTS 配置完成后如何验证

DTS 配置完,不是直接make dtbs就完事了。我习惯按下面几个步骤验证:

第一步,确认设备树有没有被正确编译进 boot 镜像。RK3588 Android12 通常用./build.sh -d rk3588-evb1-lp4-v10这样的方式选择 DTS,然后去 kernel 目录执行make dtbs,再重新打包 boot.img。

第二步,板子起来后检查设备树实际挂载情况。执行:

# 查看tcpc设备是否挂载成功 ls /sys/bus/i2c/devices/6-0022/ # 查看typec端口状态 ls /sys/class/typec/port0/

如果 6-0022 目录不存在,说明驱动 probe 失败或 I2C 地址不对,回到 2.2 节排查。

第三步,插入 PD 充电器,立刻看内核日志:

dmesg | grep -iE "tcpm|fusb|tcpc|pd"

正常情况会看到CC1: 0 -> 2之类的状态变化,然后进入 PD 协商状态机,最终出现类似POWER_SUPPLY_PROP_ONLINE的上报。如果这个过程卡住,多半是中断或 I2C 通信问题。后面第 4 节会专门展开。

3. 内核驱动修改:从 PD 协商到充电策略

DTS 只是把硬件的拓扑关系描述清楚,真正决定“能协商到多少伏多少安”“协商完怎么充电”的是内核驱动。RK3588 Android12 的 PD 相关驱动主要分布在drivers/usb/typec/tcpm/和drivers/power/supply/这两个目录下面。

3.1 tcpm 核心状态机与驱动代码位置

Linux 内核标准 tcpm 驱动位于drivers/usb/typec/tcpm/tcpm.c,它实现了一整套 Type-C 连接状态机,包括 none/unattached/attached 等基础状态,以及整套 PD 消息收发逻辑。RK3588 BSP 一般会在这个基础上做一些定制,比如修改tcpm_pd_check_request、tcpm_pd_build_request这些函数,从而改变 RDO(Request Data Object)的生成策略。

实际调试时,最常用的路径是:插入充电器,tcpm 进入SNK_DISCOVERY,然后通过 Source Capabilities 消息获得充电器支持的 PDO 列表,再据此构建 RDO 发起请求。如果你希望固定请求某个电压,可以直接在驱动里修改选择逻辑,也可以跑一个用户态策略。这里我推荐能用 DTS 配置解决的,尽量不要改内核代码,否则每次升级内核都要重新打补丁。

3.2 修改默认 PD 协商电压电流上限

RK3588 SDK 通常支持通过 tcpm_policy 驱动或者 DTS 属性限制 PD 协商范围。比如:

tcpm_policy { compatible = "rockchip,tcpm-policy"; max_voltage_mv = <9000>; max_current_ma = <2000>; max_power_mw = <18000>; };

这段配置的意思是:PD 协商时最多请求 9V/2A,即使充电器支持 20V/5A,也不会去请求超过这个范围的档位。别小看这个限制,很多电源路径设计、电感选型、PCB 走线都是按特定功率做的,超过设计上限轻则发热严重,重则烧板。

如果你需要去掉上限,比如做 100W 大功率充电,就把这几个值调大,同时检查硬件功率路径、电池充电 IC 的电流采样电阻、VBUS 走线是否满足要求。要注意的是,光改 tcpm_policy 不一定够,外置充电 IC 的充电电流上限通常也要同步修改,否则 PD 请求上去了,充电 IC 还是按老策略限流,白白浪费了协商能力。

3.3 充电器驱动与 PMIC 的功率传递

PD 协商成功只是第一步,关键是执行。在内核里,tcpm 拿到协商结果后,会把请求电压电流通过tcpm_psy_set_prop或者power_supply_changed广播出去,充电 IC 驱动需要监听这些事件并切换自己的输出。

RK3588 BSP 中,如果使用 RK806 PMIC,充电状态可以在sysfs里直接查看:

cat /sys/class/power_supply/rk806_charger/uevent

关注这几个字段:

  • POWER_SUPPLY_STATUS:充电状态,Discharging/Charging/Full
  • POWER_SUPPLY_CONSTANT_CHARGE_CURRENT_MAX:最大恒流充电电流
  • POWER_SUPPLY_CONSTANT_CHARGE_VOLTAGE_MAX:最大恒压充电电压

如果你的平台是外置充电 IC,比如南芯 SC8886,那么充电电流的目标值往往写在充电 IC 的寄存器里,驱动通过 I2C 写入。PD 请求到 9V/3A,但是充电 IC 端寄存器只配了 2A,那实际充电电流还是 2A。这也是“PD 协商到高压但充电速度不快”的一个隐藏原因。

下表是常见调试对象和对应 sysfs 路径:

调试对象路径/接口常见问题
PD 协商状态dmesg里的 tcpm 状态机日志卡在 SNK_DISCOVERY,不进入协商
Type-C 端口状态/sys/class/typec/port0/data_role角色切换失败,一直是 device
充电器信息/sys/class/power_supply/usb/ueventtype 为 N/A,充电器类型识别失败
实际充电电流PMIC 或充电 IC 对应的 power_supply 节点电流上不去,与 PD 请求值不一致

3.4 一个典型实战修改:让 PD 快充优先,兜底 QC2.0

PD 不是唯一的快充协议,市面上很多充电器同时支持 PD 和 QC2.0/QC3.0。在 RK3588 平台上做产品时,有一个很常见的需求:优先走 PD 协商,如果在规定时间内协商失败,就退回 QC 或者普通 5V 充电,避免用户插一个“假 PD”充电器导致充电功率过低。

实现思路不复杂。在 tcpm_policy 驱动或对应的 charger 驱动里加一个超时判断,比如 1 秒内没有收到 Source Capabilities,就发送tcpm_psy_set_online通知上层切换到 QC 流程。这里要特别提醒:QC 握手是通过 D+/D- 电压实现的,与 PD 的 CC 线完全独立,切换时要注意 VBUS 电压爬升曲线,避免直接从 5V 跳到 12V 导致后端器件过压。

我实际调过的一个案子,就是 PD 协商超时后直接切 QC2.0,通过 USB 控制器的 D+/D- 开关,把 D+ 拉高到 0.6V、D- 拉高到 0.6V,让充电器输出 12V。因为 RK3588 的 usb2phy 内部带了 BC1.2 模块,这套逻辑在驱动里实现起来并不难。关键是切换时机和电压调节节奏要控制好,不然容易引起充电器保护。

4. 调试方法、常见问题与排查技巧实录

PD 充电的调试,比普通 USB 充电复杂得多,因为协议协商是异步的,状态多,而且跟线缆质量、充电器固件、CC 引脚接触电阻都有关。我建议每个人在动手前先把日志抓取工具准备好,省得到时候两眼一抹黑。

4.1 用 tcpm 状态机与日志定位协商卡点

插入 PD 充电器后,快速执行下面这条命令,把协议交互过程完整体现出来:

logcat -d | grep -iE "tcpc|tcpm|typec|pd" dmesg | grep -iE "tcpm|fusb|tcpci"

正常的 PD 协商流程中,日志会呈现一条清晰的状态链:

  • CC1: 0 -> 2表示检测到线缆插入,CC 电平从开路变成拉低
  • SNK_ATTACHED表示以 Sink(受电方)身份接入
  • PD_DISCOVERY表示开始运行 PD 发现流程
  • Source_Caps收到充电器能力列表
  • SEND_RDO发送请求并收到 Accept
  • SNK_READY表示协商完成,可以开始充电

如果日志卡在SNK_DISCOVERY,大概率是 PD 消息收发失败。这时候先判断是中断问题还是 I2C 通信问题:看dmesg里有没有频繁的 I2C read/write error,有的话重点查 TCPC 供电和 I2C 上拉;没有的话,用示波器抓 CC 线的拨动波形,看 TCPC 的 INT 脚有没有产生下降沿。

如果日志里压根没有CC1: 0 -> 2,那问题基本出在 TCPC 驱动没有正确响应中断,先回 DTS 查中断 GPIO 和 pinctrl。还有一种可能是 CC 引脚对地短路或者接触不良,这个只能换线、换座子验证。

4.2 常见问题速查表

我整理了一份问题速查表,覆盖了日常调试中最容易遇到的几类问题:

现象可能原因排查/解决建议
插 PD 充电器无任何反应TCPC 中断没触发;VBUS 没供上查 DTS 中断脚,dmesg 看是否有 CC 状态变化;用万用表量 VBUS 有无 5V
能识别充电器,但电流锁在 5V/1.5APD 协商失败,退回默认 USB 充电抓 tcpm 日志,看是否收到 Source_Caps;检查 TCPC I2C 通信稳定性
协商到 9V/2A,但系统显示充电极慢充电 IC 寄存器限流读充电 IC current limit 寄存器;检查 PMIC 充电配置
插拔过程中偶发系统重启角色切换时 VBUS 冲突检查 VBUS 供电设计,避免 Device/Host 切换时电源反灌;DTS 中不要乱开 vbus-always-on
扩展坞插上去无法识别 USB 设备TCPM 角色切换没切到 Host看 typec/data_role 是否切换到 host;检查 USB3 PHY 的 extcon 引用
拔线后系统仍认为在充电CC 中断丢失;extcon 没有上报拔插事件查 TCPC 拔插中断;确认 extcon 驱动是否调用了 extcon_set_state

其中“插拔过程中偶发系统重启”这个问题最隐蔽,因为它不是协议问题,而是电源设计问题。当角色从 Device 切到 Host 时,VBUS 要从“接收外部电源”突然变成“对外输出 5V”,如果电源路径上的 MOS 管切换时序没做好,就会导致 VBUS 和系统电源短暂短路,触发 PMIC 过流保护,表现就是整机断电重启。遇到这种情况,优先抓 PMIC 的中断日志,确认是不是 OVP 或 OCP 触发。

4.3 调试工具与实际项目中的避坑经验

软件层面,我强烈建议准备一个 PD 协议分析仪,不需要太贵,能解析 PD 消息、显示电压电流波形就行。没有分析仪的时候,也可以用 USB 电流电压表,就是那种小屏幕显示电压电流的功率计,配合 dmesg 日志,能解决 80% 的问题。

硬件层面,一个容易被忽略的点是 CC 引脚上的 5.1k 下拉电阻和 Rd/Ra 检测。Type-C 设备作为 Sink 时,CC1/CC2 需要分别接 5.1k 下拉电阻到地,TCPC 靠检测这个电阻来识别插入方向。很多公版原理图在 Type-C 座子设计时没有仔细核对这两颗电阻的精度,用了 1% 甚至 5% 的电阻,导致 CC 电压偏离指定范围,TCPC 误判方向。我遇到过一块板子,正插能充电,反插就概率性没反应,最后查出来就是其中一颗 5.1k 电阻虚焊。

另外一个经验是,RK3588 Android12 平台在修改 DTS 后,一定要清掉旧的 dtb 缓存。有些编译系统会缓存 dtb,导致你改了 DTS 重新打包,但实际跑的还是旧设备树。检查方法很简单:开机后在板子上执行:

cat /proc/device-tree/model cat /proc/device-tree/chosen/rk-dts-root/status 2>/dev/null || true

再看 dmesg 里打印的 DTS 文件名,确认是不是你刚改的那个。

4.4 内核配置裁剪对 PD 功能的影响

最后提一个很容易被忽略的地方:内核 Kconfig 裁剪。很多定制 ROM 为了精简内核,会把不用的驱动关掉,但 Type-C 相关配置一旦被裁剪,BD 问题非常隐蔽。

下面是 RK3588 Android12 PD 功能相关的主要配置项:

CONFIG_TYPEC=y CONFIG_TYPEC_TCPM=y CONFIG_TYPEC_TCPCI=y CONFIG_TYPEC_FUSB302=y CONFIG_POWER_SUPPLY=y CONFIG_CHARGER_RK806=y CONFIG_RK_HEADSET_DET=y

如果确认 DTS 没问题但驱动始终不 probe,就去查内核 .config 里这些选项有没有被关掉。CONFIG_TYPEC_FUSB302对应 FUSB302 这类 TCPC 芯片的驱动,如果你用的是别的型号,可能需要CONFIG_TYPEC_HUSB311等对应配置。另一个经常被误关的是CONFIG_RK_HEADSET_DET,它负责 Type-C 耳机和模拟音频切换,和 PD 充电共享一部分 extcon 逻辑,关掉之后 Type-C 口的功能会变得很奇怪。

另外,Android 12 上还有一个隐藏依赖:/sys/class/typec/port0/下的接口,会由CONFIG_TYPEC和CONFIG_TYPEC_CLASS控制。如果系统里没有这个目录,Android 的 UsbManager 和 Settings 里的充电相关显示都会失效,即使内核驱动本身能正常协商。这个坑排查起来很费时间,因为 Mar 上看起来一切正常,就是上层功能不刷新。

这块整套调下来,我个人最大的体会是:PD 充电不是“改个 DTS 就能跑”的模块,它是一个完整的状态机系统,DTS 负责布线,内核驱动负责决策,充电 IC 负责执行,链路上一环抠不紧,最终就表现为充电慢、不充电、插拔重启这类看似玄学的问题。建议你拿到一个新板子时,先把基础 Sink 功能跑通——只是单纯接受充电器供电,不做 Host、不做数据,确认 PD 能协商到目标电压电流之后,再去扩展 DRP、PPS、OTG 这些花活。这样出了问题,每一步的边界都很清楚,定位起来会轻松很多。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询